--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Commit 4b619f385d3d0199b9749e5d288523e40c23c933
Parents : 9cf33ec
Author : Mark Qvist <mark@unsigned.io>
Date : 2022-10-08T17:17:59+02:00
Updated KivyMD
Changes
99 files changed, 6183 insertions(+), 3871 deletions(-)
Diff
diff --git a/sbapp/kivymd/__init__.py b/sbapp/kivymd/__init__.py
index a2d8f8f3..fa6cb272 100644
--- a/sbapp/kivymd/__init__.py
+++ b/sbapp/kivymd/__init__.py
@@ -49,6 +49,9 @@ images_path = os.path.join(path, f"images{os.sep}")
uix_path = os.path.join(path, "uix")
"""Path to uix directory."""
+glsl_path = os.path.join(path, "data", "glsl")
+"""Path to glsl directory."""
+
_log_message = (
"KivyMD:"
+ (" Release" if release else "")
diff --git a/sbapp/kivymd/app.py b/sbapp/kivymd/app.py
index abae5f06..dfe3b274 100644
--- a/sbapp/kivymd/app.py
+++ b/sbapp/kivymd/app.py
@@ -43,9 +43,10 @@ __all__ = ("MDApp",)
import os
from kivy.app import App
+from kivy.clock import Clock
from kivy.lang import Builder
from kivy.logger import Logger
-from kivy.properties import ObjectProperty
+from kivy.properties import ObjectProperty, StringProperty
from kivymd.theming import ThemeManager
@@ -56,13 +57,16 @@ class FpsMonitoring:
def fps_monitor_start(self) -> None:
"""Adds a monitor to the main application window."""
- from kivy.core.window import Window
+ def add_monitor(*args):
+ from kivy.core.window import Window
- from kivymd.utils.fpsmonitor import FpsMonitor
+ from kivymd.utils.fpsmonitor import FpsMonitor
- monitor = FpsMonitor()
- monitor.start()
- Window.add_widget(monitor)
+ monitor = FpsMonitor()
+ monitor.start()
+ Window.add_widget(monitor)
+
+ Clock.schedule_once(add_monitor)
class MDApp(App, FpsMonitoring):
@@ -71,6 +75,16 @@ class MDApp(App, FpsMonitoring):
information.
"""
+ icon = StringProperty("kivymd/images/logo/kivymd-icon-512.png")
+ """
+ See :attr:`~kivy.app.App.icon` attribute for more information.
+
+ .. versionadded:: 1.1.0
+
+ :attr:`icon` is an :class:`~kivy.properties.StringProperty`
+ adn default to `kivymd/images/logo/kivymd-icon-512.png`.
+ """
+
theme_cls = ObjectProperty()
"""
Instance of :class:`~ThemeManager` class.
diff --git a/sbapp/kivymd/color_definitions.py b/sbapp/kivymd/color_definitions.py
index 47a2edfa..e4a7de46 100755
--- a/sbapp/kivymd/color_definitions.py
+++ b/sbapp/kivymd/color_definitions.py
@@ -412,7 +412,7 @@ To demonstrate the shades of the palette, you can run the following code:
self.screen = Factory.Root()
for name_tab in colors.keys():
- tab = Tab(text=name_tab)
+ tab = Tab(title=name_tab)
self.screen.ids.android_tabs.add_widget(tab)
return self.screen
@@ -427,7 +427,7 @@ To demonstrate the shades of the palette, you can run the following code:
{
"viewclass": "ItemColor",
"md_bg_color": colors[tab_text][value_color],
- "text": value_color,
+ "title": value_color,
}
)
diff --git a/sbapp/kivymd/data/glsl/elevation/elevation.frag b/sbapp/kivymd/data/glsl/elevation/elevation.frag
new file mode 100644
index 00000000..03f042ac
--- /dev/null
+++ b/sbapp/kivymd/data/glsl/elevation/elevation.frag
@@ -0,0 +1,51 @@
+/*
+The shader code has been refactored for the KivyMD library.
+You can find the original code of this shaders at the links:
+
+https://www.shadertoy.com/view/WtdSDs
+https://www.shadertoy.com/view/fsdyzB
+
+Additional thanks to iq for optimizing conditional block for individual
+corner radius:
+https://iquilezles.org/articles/distfunctions
+*/
+
+// For lower opengl version
+
+float custom_smoothstep(float a, float b, float x) {
+ float t = clamp((x - a) / (b - a), 0.0, 1.0);
+ return t * t * (3.0 - 2.0 * t);
+}
+
+float roundedBoxSDF(vec2 centerPosition, vec2 size, vec4 radius) {
+ radius.xy = (centerPosition.x > 0.0) ? radius.xy : radius.zw;
+ radius.x = (centerPosition.y > 0.0) ? radius.x : radius.y;
+
+ vec2 q = abs(centerPosition) - (size - shadow_softness) + radius.x;
+ return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - radius.x;
+}
+
+void mainImage(out vec4 fragColor, in vec2 fragCoord) {
+ // Smooth the result (free antialiasing).
+ float edge0 = 0.0;
+ float smoothedAlpha = 1.0 - custom_smoothstep(0.0, edge0, 1.0);
+ // Get the resultant shape.
+ vec4 quadColor = mix(
+ vec4(
+ shadow_color[0],
+ shadow_color[1],
+ shadow_color[2],
+ 0.0
+ ),
+ shadow_color,
+ smoothedAlpha
+ );
+ // Apply a drop shadow effect.
+ float shadowDistance = roundedBoxSDF(
+ fragCoord.xy - mouse.xy - (size / 2.0), size / 2.0, shadow_radius
+ );
+ float shadowAlpha = 1.0 - custom_smoothstep(
+ -shadow_softness, shadow_softness, shadowDistance
+ );
+ fragColor = mix(quadColor, shadow_color, shadowAlpha - smoothedAlpha);
+}
diff --git a/sbapp/kivymd/data/glsl/elevation/header.frag b/sbapp/kivymd/data/glsl/elevation/header.frag
new file mode 100644
index 00000000..acb3a8a5
--- /dev/null
+++ b/sbapp/kivymd/data/glsl/elevation/header.frag
@@ -0,0 +1,10 @@
+#ifdef GL_ES
+ precision highp float;
+#endif
+
+uniform vec4 resolution;
+uniform vec4 mouse;
+uniform vec2 size;
+uniform vec4 shadow_radius;
+uniform float shadow_softness;
+uniform vec4 shadow_color;
diff --git a/sbapp/kivymd/data/glsl/elevation/main.frag b/sbapp/kivymd/data/glsl/elevation/main.frag
new file mode 100644
index 00000000..d9b87129
--- /dev/null
+++ b/sbapp/kivymd/data/glsl/elevation/main.frag
@@ -0,0 +1,10 @@
+vec2 gfc(in vec4 fc) {
+ vec2 canvas_pos = resolution.zw;
+ vec2 uv = fc.xy;
+ uv.y -= canvas_pos.y;
+ return uv;
+}
+
+void main(void) {
+ mainImage(gl_FragColor, gfc(gl_FragCoord));
+}
diff --git a/sbapp/kivymd/images/folder.png b/sbapp/kivymd/images/folder.png
index 58fed42b..fe0ed5ba 100644
Binary files a/sbapp/kivymd/images/folder.png and b/sbapp/kivymd/images/folder.png differ
diff --git a/sbapp/kivymd/images/quad_shadow-0.png b/sbapp/kivymd/images/quad_shadow-0.png
deleted file mode 100644
index f847665c..00000000
Binary files a/sbapp/kivymd/images/quad_shadow-0.png and /dev/null differ
diff --git a/sbapp/kivymd/images/quad_shadow-1.png b/sbapp/kivymd/images/quad_shadow-1.png
deleted file mode 100644
index fea057ff..00000000
Binary files a/sbapp/kivymd/images/quad_shadow-1.png and /dev/null differ
diff --git a/sbapp/kivymd/images/quad_shadow-2.png b/sbapp/kivymd/images/quad_shadow-2.png
deleted file mode 100644
index 1949d5e7..00000000
Binary files a/sbapp/kivymd/images/quad_shadow-2.png and /dev/null differ
diff --git a/sbapp/kivymd/images/quad_shadow.atlas b/sbapp/kivymd/images/quad_shadow.atlas
deleted file mode 100644
index 68e0aad2..00000000
--- a/sbapp/kivymd/images/quad_shadow.atlas
+++ /dev/null
@@ -1 +0,0 @@
-{"quad_shadow-1.png": {"20": [2, 136, 128, 128], "21": [132, 136, 128, 128], "22": [262, 136, 128, 128], "23": [2, 6, 128, 128], "19": [132, 266, 128, 128], "18": [2, 266, 128, 128], "1": [262, 266, 128, 128], "3": [262, 6, 128, 128], "2": [132, 6, 128, 128]}, "quad_shadow-0.png": {"11": [262, 266, 128, 128], "10": [132, 266, 128, 128], "13": [132, 136, 128, 128], "12": [2, 136, 128, 128], "15": [2, 6, 128, 128], "14": [262, 136, 128, 128], "17": [262, 6, 128, 128], "16": [132, 6, 128, 128], "0": [2, 266, 128, 128]}, "quad_shadow-2.png": {"5": [132, 266, 128, 128], "4": [2, 266, 128, 128], "7": [2, 136, 128, 128], "6": [262, 266, 128, 128], "9": [262, 136, 128, 128], "8": [132, 136, 128, 128]}}
\ No newline at end of file
diff --git a/sbapp/kivymd/images/rec_shadow-0.png b/sbapp/kivymd/images/rec_shadow-0.png
deleted file mode 100644
index f02b919a..00000000
Binary files a/sbapp/kivymd/images/rec_shadow-0.png and /dev/null differ
diff --git a/sbapp/kivymd/images/rec_shadow-1.png b/sbapp/kivymd/images/rec_shadow-1.png
deleted file mode 100644
index f752fd26..00000000
Binary files a/sbapp/kivymd/images/rec_shadow-1.png and /dev/null differ
diff --git a/sbapp/kivymd/images/rec_shadow.atlas b/sbapp/kivymd/images/rec_shadow.atlas
deleted file mode 100644
index 71b0e9d6..00000000
--- a/sbapp/kivymd/images/rec_shadow.atlas
+++ /dev/null
@@ -1 +0,0 @@
-{"rec_shadow-1.png": {"20": [2, 266, 256, 128], "21": [260, 266, 256, 128], "22": [518, 266, 256, 128], "23": [776, 266, 256, 128], "3": [260, 136, 256, 128], "2": [2, 136, 256, 128], "5": [776, 136, 256, 128], "4": [518, 136, 256, 128], "7": [260, 6, 256, 128], "6": [2, 6, 256, 128], "9": [776, 6, 256, 128], "8": [518, 6, 256, 128]}, "rec_shadow-0.png": {"11": [518, 266, 256, 128], "10": [260, 266, 256, 128], "13": [2, 136, 256, 128], "12": [776, 266, 256, 128], "15": [518, 136, 256, 128], "14": [260, 136, 256, 128], "17": [2, 6, 256, 128], "16": [776, 136, 256, 128], "19": [518, 6, 256, 128], "18": [260, 6, 256, 128], "1": [776, 6, 256, 128], "0": [2, 266, 256, 128]}}
\ No newline at end of file
diff --git a/sbapp/kivymd/images/rec_st_shadow-0.png b/sbapp/kivymd/images/rec_st_shadow-0.png
deleted file mode 100644
index 887327db..00000000
Binary files a/sbapp/kivymd/images/rec_st_shadow-0.png and /dev/null differ
diff --git a/sbapp/kivymd/images/rec_st_shadow-1.png b/sbapp/kivymd/images/rec_st_shadow-1.png
deleted file mode 100644
index 759ee652..00000000
Binary files a/sbapp/kivymd/images/rec_st_shadow-1.png and /dev/null differ
diff --git a/sbapp/kivymd/images/rec_st_shadow-2.png b/sbapp/kivymd/images/rec_st_shadow-2.png
deleted file mode 100644
index e9fdaccc..00000000
Binary files a/sbapp/kivymd/images/rec_st_shadow-2.png and /dev/null differ
diff --git a/sbapp/kivymd/images/rec_st_shadow.atlas b/sbapp/kivymd/images/rec_st_shadow.atlas
deleted file mode 100644
index d4c24abe..00000000
--- a/sbapp/kivymd/images/rec_st_shadow.atlas
+++ /dev/null
@@ -1 +0,0 @@
-{"rec_st_shadow-0.png": {"11": [262, 138, 128, 256], "10": [132, 138, 128, 256], "13": [522, 138, 128, 256], "12": [392, 138, 128, 256], "15": [782, 138, 128, 256], "14": [652, 138, 128, 256], "16": [912, 138, 128, 256], "0": [2, 138, 128, 256]}, "rec_st_shadow-1.png": {"20": [522, 138, 128, 256], "21": [652, 138, 128, 256], "17": [2, 138, 128, 256], "23": [912, 138, 128, 256], "19": [262, 138, 128, 256], "18": [132, 138, 128, 256], "22": [782, 138, 128, 256], "1": [392, 138, 128, 256]}, "rec_st_shadow-2.png": {"3": [132, 138, 128, 256], "2": [2, 138, 128, 256], "5": [392, 138, 128, 256], "4": [262, 138, 128, 256], "7": [652, 138, 128, 256], "6": [522, 138, 128, 256], "9": [912, 138, 128, 256], "8": [782, 138, 128, 256]}}
\ No newline at end of file
diff --git a/sbapp/kivymd/images/round_shadow-0.png b/sbapp/kivymd/images/round_shadow-0.png
deleted file mode 100644
index 26d98405..00000000
Binary files a/sbapp/kivymd/images/round_shadow-0.png and /dev/null differ
diff --git a/sbapp/kivymd/images/round_shadow-1.png b/sbapp/kivymd/images/round_shadow-1.png
deleted file mode 100644
index d0f4c0fd..00000000
Binary files a/sbapp/kivymd/images/round_shadow-1.png and /dev/null differ
diff --git a/sbapp/kivymd/images/round_shadow-2.png b/sbapp/kivymd/images/round_shadow-2.png
deleted file mode 100644
index d5feef2c..00000000
Binary files a/sbapp/kivymd/images/round_shadow-2.png and /dev/null differ
diff --git a/sbapp/kivymd/images/round_shadow.atlas b/sbapp/kivymd/images/round_shadow.atlas
deleted file mode 100644
index f25016dc..00000000
--- a/sbapp/kivymd/images/round_shadow.atlas
+++ /dev/null
@@ -1 +0,0 @@
-{"round_shadow-1.png": {"20": [2, 136, 128, 128], "21": [132, 136, 128, 128], "22": [262, 136, 128, 128], "23": [2, 6, 128, 128], "19": [132, 266, 128, 128], "18": [2, 266, 128, 128], "1": [262, 266, 128, 128], "3": [262, 6, 128, 128], "2": [132, 6, 128, 128]}, "round_shadow-0.png": {"11": [262, 266, 128, 128], "10": [132, 266, 128, 128], "13": [132, 136, 128, 128], "12": [2, 136, 128, 128], "15": [2, 6, 128, 128], "14": [262, 136, 128, 128], "17": [262, 6, 128, 128], "16": [132, 6, 128, 128], "0": [2, 266, 128, 128]}, "round_shadow-2.png": {"5": [132, 266, 128, 128], "4": [2, 266, 128, 128], "7": [2, 136, 128, 128], "6": [262, 266, 128, 128], "9": [262, 136, 128, 128], "8": [132, 136, 128, 128]}}
\ No newline at end of file
diff --git a/sbapp/kivymd/tests/pyinstaller/test_pyinstaller_packaging.py b/sbapp/kivymd/tests/pyinstaller/test_pyinstaller_packaging.py
index 801f5bf5..3b81fdfd 100644
--- a/sbapp/kivymd/tests/pyinstaller/test_pyinstaller_packaging.py
+++ b/sbapp/kivymd/tests/pyinstaller/test_pyinstaller_packaging.py
@@ -35,8 +35,15 @@ assert "Icons" in LabelBase._fonts.keys() # NOQA
images = os.listdir(kivymd.images_path)
print(images)
+assert "logo" in images
+assert "alpha_layer.png" in images
+assert "black.png" in images
+assert "blue.png" in images
+assert "red.png" in images
+assert "green.png" in images
+assert "yellow.png" in images
assert "folder.png" in images
-assert "rec_shadow.atlas" in images
+assert "transparent.png" in images
"""
)
pyi_main.run(
diff --git a/sbapp/kivymd/tests/test_create_project.py b/sbapp/kivymd/tests/test_create_project.py
index 3122f1e9..31265fa3 100644
--- a/sbapp/kivymd/tests/test_create_project.py
+++ b/sbapp/kivymd/tests/test_create_project.py
@@ -1,14 +1,13 @@
def test_create_project():
import os
- import sys
os.system(
- f"{sys.executable} -m kivymd.tools.patterns.create_project "
+ f"python3.10 -m kivymd.tools.patterns.create_project "
f"MVC "
f"{os.path.expanduser('~')} "
f"TestProject "
- f"{sys.executable} "
- f"master "
+ f"python3.10 "
+ f"stable "
f"--name_screen TestProjectScreen "
f"--name_database restdb "
f"--use_hotreload yes"
diff --git a/sbapp/kivymd/theming.py b/sbapp/kivymd/theming.py
index 638ae603..b0fa0daf 100755
--- a/sbapp/kivymd/theming.py
+++ b/sbapp/kivymd/theming.py
@@ -212,9 +212,8 @@ respects, the theming stays as documented.
dictionary :attr:`kivymd.color_definition.colors`.
"""
-
+from kivy.animation import Animation
from kivy.app import App
-from kivy.atlas import Atlas
from kivy.clock import Clock
from kivy.core.window import Window
from kivy.event import EventDispatcher
@@ -224,13 +223,13 @@ from kivy.properties import (
BooleanProperty,
ColorProperty,
DictProperty,
+ NumericProperty,
ObjectProperty,
OptionProperty,
StringProperty,
)
from kivy.utils import get_color_from_hex
-from kivymd import images_path
from kivymd.color_definitions import colors, hue, palette
from kivymd.font_definitions import theme_font_styles
from kivymd.material_resources import DEVICE_IOS, DEVICE_TYPE
@@ -624,6 +623,152 @@ class ThemeManager(EventDispatcher):
and defaults to `'M2'`.
"""
+ theme_style_switch_animation = BooleanProperty(False)
+ """
+ Animate app colors when switching app color scheme ('Dark/light').
+
+ .. versionadded:: 1.1.0
+
+ .. tabs::
+
+ .. tab:: Declarative KV style
+
+ .. code-block:: python
+
+ from kivy.lang import Builder
+
+ from kivymd.app import MDApp
+
+ KV = '''
+ MDScreen:
+
+ MDCard:
+ orientation: "vertical"
+ padding: 0, 0, 0 , "36dp"
+ size_hint: .5, .5
+ pos_hint: {"center_x": .5, "center_y": .5}
+ elevation: 4
+ shadow_radius: 6
+ shadow_offset: 0, 2
+
+ MDLabel:
+ text: "Theme style - {}".format(app.theme_cls.theme_style)
+ halign: "center"
+ valign: "center"
+ bold: True
+ font_style: "H5"
+
+ MDRaisedButton:
+ text: "Set theme"
+ on_release: app.switch_theme_style()
+ pos_hint: {"center_x": .5}
+ '''
+
+
+ class Example(MDApp):
+ def build(self):
+ self.theme_cls.theme_style_switch_animation = True
+ self.theme_cls.theme_style = "Dark"
+ self.theme_cls.primary_palette = "Orange"
+ return Builder.load_string(KV)
+
+ def switch_theme_style(self):
+ self.theme_cls.primary_palette = (
+ "Orange" if self.theme_cls.primary_palette == "Red" else "Red"
+ )
+ self.theme_cls.theme_style = (
+ "Dark" if self.theme_cls.theme_style == "Light" else "Light"
+ )
+
+
+ Example().run()
+
+ .. tab:: Declarative python style
+
+ .. code-block:: python
+
+ from kivymd.app import MDApp
+ from kivymd.uix.button import MDRaisedButton
+ from kivymd.uix.card import MDCard
+ from kivymd.uix.label import MDLabel
+ from kivymd.uix.screen import MDScreen
+
+
+ class Example(MDApp):
+ def build(self):
+ self.theme_cls.theme_style_switch_animation = True
+ self.theme_cls.theme_style = "Dark"
+ self.theme_cls.primary_palette = "Orange"
+ return (
+ MDScreen(
+ MDCard(
+ MDLabel(
+ id="label",
+ text="Theme style - {}".format(self.theme_cls.theme_style),
+ halign="center",
+ valign="center",
+ bold=True,
+ font_style="H5",
+ ),
+ MDRaisedButton(
+ text="Set theme",
+ on_release=self.switch_theme_style,
+ pos_hint={"center_x": 0.5},
+ ),
+ id="card",
+ orientation="vertical",
+ padding=(0, 0, 0, "36dp"),
+ size_hint=(0.5, 0.5),
+ pos_hint={"center_x": 0.5, "center_y": 0.5},
+ elevation=4,
+ shadow_radius=6,
+ shadow_offset=(0, 2),
+ )
+ )
+ )
+
+ def switch_theme_style(self, *args):
+ self.theme_cls.primary_palette = (
+ "Orange" if self.theme_cls.primary_palette == "Red" else "Red"
+ )
+ self.theme_cls.theme_style = (
+ "Dark" if self.theme_cls.theme_style == "Light" else "Light"
+ )
+ self.root.ids.card.ids.label.text = (
+ "Theme style - {}".format(self.theme_cls.theme_style)
+ )
+
+
+ Example().run()
+
+ .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/theme-style-switch-animation.gif
+ :align: center
+
+ :attr:`theme_style_switch_animation` is an :class:`~kivy.properties.BooleanProperty`
+ and defaults to `False`.
+ """
+
+ theme_style_switch_animation_duration = NumericProperty(0.2)
+ """
+ Duration of the animation of switching the color scheme of the application
+ ("Dark/light").
+
+ .. versionadded:: 1.1.0
+
+ .. code-block:: python
+
+ class Example(MDApp):
+ def build(self):
+ self.theme_cls.theme_style_switch_animation = True
+ self.theme_cls.theme_style_switch_animation_duration = 0.8
+
+ .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/theme-style-switch-animation-duration.gif
+ :align: center
+
+ :attr:`theme_style_switch_animation_duration` is an :class:`~kivy.properties.NumericProperty`
+ and defaults to `0.2`.
+ """
+
theme_style = OptionProperty("Light", options=["Light", "Dark"])
"""
App theme style.
@@ -1184,14 +1329,22 @@ class ThemeManager(EventDispatcher):
):
self.set_clearcolor_by_theme_style(theme_style)
- set_clearcolor = BooleanProperty(True)
+ _set_clearcolor = False
def set_clearcolor_by_theme_style(self, theme_style):
- if not self.set_clearcolor:
- return
- Window.clearcolor = get_color_from_hex(
- self.colors[theme_style]["Background"]
- )
+ if self.theme_style_switch_animation and self._set_clearcolor:
+ Animation(
+ clearcolor=get_color_from_hex(
+ self.colors[theme_style]["Background"]
+ ),
+ d=self.theme_style_switch_animation_duration,
+ t="linear",
+ ).start(Window)
+ else:
+ Window.clearcolor = get_color_from_hex(
+ self.colors[theme_style]["Background"]
+ )
+ self._set_clearcolor = True
# Font name, size (sp), always caps, letter spacing (sp).
font_styles = DictProperty(
@@ -1398,10 +1551,6 @@ class ThemeManager(EventDispatcher):
def __init__(self, **kwargs):
super().__init__(**kwargs)
- self.rec_shadow = Atlas(f"{images_path}rec_shadow.atlas")
- self.rec_st_shadow = Atlas(f"{images_path}rec_st_shadow.atlas")
- self.quad_shadow = Atlas(f"{images_path}quad_shadow.atlas")
- self.round_shadow = Atlas(f"{images_path}round_shadow.atlas")
Clock.schedule_once(lambda x: self.on_theme_style(0, self.theme_style))
self._determine_device_orientation(None, Window.size)
Window.bind(size=self._determine_device_orientation)
@@ -1487,6 +1636,16 @@ class ThemableBehavior(EventDispatcher):
"""
def __init__(self, **kwargs):
+ self.unbind_properties = [
+ "theme_style",
+ "material_style",
+ "device_orientation",
+ "primary_color",
+ "primary_palette",
+ "accent_palette",
+ "text_color",
+ ]
+
if self.theme_cls is not None:
pass
else:
@@ -1507,3 +1666,24 @@ class ThemableBehavior(EventDispatcher):
)
self.theme_cls = App.get_running_app().theme_cls
super().__init__(**kwargs)
+
+ def dec_disabled(self, *args, **kwargs) -> None:
+ callabacks = self.theme_cls.get_property_observers("theme_style")
+
+ for callaback in callabacks:
+ try:
+ if hasattr(callaback, "proxy") and hasattr(
+ callaback.proxy, "theme_cls"
+ ):
+ for property_name in self.unbind_properties:
+ self.theme_cls.unbind(
+ **{
+ property_name: getattr(
+ callaback.proxy, callaback.method_name
+ )
+ }
+ )
+ except ReferenceError:
+ pass
+
+ super().dec_disabled(*args, **kwargs)
diff --git a/sbapp/kivymd/tools/packaging/pyinstaller/hook-kivymd.py b/sbapp/kivymd/tools/packaging/pyinstaller/hook-kivymd.py
index d2cb9a5c..040d103a 100644
--- a/sbapp/kivymd/tools/packaging/pyinstaller/hook-kivymd.py
+++ b/sbapp/kivymd/tools/packaging/pyinstaller/hook-kivymd.py
@@ -13,6 +13,18 @@ from pathlib import Path
import kivymd
datas = [
+ # Add `.frag` files from the `kivymd/data/glsl/elevation` directory.
+ (
+ str(Path(kivymd.glsl_path).joinpath("elevation")) + os.sep,
+ str(
+ Path("kivymd").joinpath(
+ str(Path(kivymd.glsl_path)).split(str(Path("kivymd")) + os.sep)[
+ 1
+ ]
+ + f"{os.sep}elevation"
+ )
+ ),
+ ),
# Add `.ttf` files from the `kivymd/fonts` directory.
(
kivymd.fonts_path,
diff --git a/sbapp/kivymd/uix/backdrop/backdrop.kv b/sbapp/kivymd/uix/backdrop/backdrop.kv
index 372c7549..2f56d5be 100644
--- a/sbapp/kivymd/uix/backdrop/backdrop.kv
+++ b/sbapp/kivymd/uix/backdrop/backdrop.kv
@@ -35,8 +35,8 @@
if not root.front_layer_color \
else root.front_layer_color
radius:
- [root.radius_left, root.radius_left,
- root.radius_right, root.radius_right]
+ [root.radius_left, root.radius_right,
+ 0, 0]
OneLineListItem:
id: header_button
diff --git a/sbapp/kivymd/uix/backdrop/backdrop.py b/sbapp/kivymd/uix/backdrop/backdrop.py
index 4262d588..604ab13b 100644
--- a/sbapp/kivymd/uix/backdrop/backdrop.py
+++ b/sbapp/kivymd/uix/backdrop/backdrop.py
@@ -202,7 +202,6 @@ from kivy.uix.boxlayout import BoxLayout
from kivymd import uix_path
from kivymd.theming import ThemableBehavior
-from kivymd.uix.behaviors import FakeRectangularElevationBehavior
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.card import MDCard
from kivymd.uix.floatlayout import MDFloatLayout
@@ -528,5 +527,5 @@ class _BackLayer(BoxLayout):
pass
-class _FrontLayer(MDCard, FakeRectangularElevationBehavior):
+class _FrontLayer(MDCard):
pass
diff --git a/sbapp/kivymd/uix/banner/banner.py b/sbapp/kivymd/uix/banner/banner.py
index 7feb5c95..fb87221d 100644
--- a/sbapp/kivymd/uix/banner/banner.py
+++ b/sbapp/kivymd/uix/banner/banner.py
@@ -35,7 +35,7 @@ Usage
MDTopAppBar:
id: toolbar
title: "Example Banners"
- elevation: 10
+ elevation: 4
pos_hint: {'top': 1}
MDBoxLayout:
@@ -157,7 +157,6 @@ from kivy.properties import (
from kivy.uix.widget import Widget
from kivymd import uix_path
-from kivymd.uix.behaviors import FakeRectangularElevationBehavior
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.button import MDFlatButton
from kivymd.uix.card import MDCard
@@ -177,7 +176,7 @@ with open(
Builder.load_string(kv_file.read())
-class MDBanner(MDCard, FakeRectangularElevationBehavior):
+class MDBanner(MDCard):
vertical_pad = NumericProperty(dp(68))
"""
Indent the banner at the top of the screen.
diff --git a/sbapp/kivymd/uix/behaviors/__init__.py b/sbapp/kivymd/uix/behaviors/__init__.py
index 9461c39b..e89ed10a 100755
--- a/sbapp/kivymd/uix/behaviors/__init__.py
+++ b/sbapp/kivymd/uix/behaviors/__init__.py
@@ -11,18 +11,20 @@ from .backgroundcolor_behavior import (
)
# flake8: NOQA
-from .declarative_bahavior import DeclarativeBehavior
+from .declarative_behavior import DeclarativeBehavior
from .elevation import (
CircularElevationBehavior,
CommonElevationBehavior,
FakeCircularElevationBehavior,
FakeRectangularElevationBehavior,
- ObservableShadow,
RectangularElevationBehavior,
RoundedRectangularElevationBehavior,
)
from .magic_behavior import MagicBehavior
from .ripple_behavior import CircularRippleBehavior, RectangularRippleBehavior
+from .rotate_behavior import RotateBehavior
+from .scale_behavior import ScaleBehavior
+from .stencil_behavior import StencilBehavior
from .touch_behavior import TouchBehavior
from .hover_behavior import HoverBehavior # isort:skip
diff --git a/sbapp/kivymd/uix/behaviors/backgroundcolor_behavior.py b/sbapp/kivymd/uix/behaviors/backgroundcolor_behavior.py
index d96fd60c..4c4649bf 100755
--- a/sbapp/kivymd/uix/behaviors/backgroundcolor_behavior.py
+++ b/sbapp/kivymd/uix/behaviors/backgroundcolor_behavior.py
@@ -7,8 +7,9 @@ Behaviors/Background Color
__all__ = ("BackgroundColorBehavior", "SpecificBackgroundColorBehavior")
-from typing import List
+from typing import List, Union
+from kivy.animation import Animation
from kivy.lang import Builder
from kivy.properties import (
ColorProperty,
@@ -24,8 +25,6 @@ from kivy.utils import get_color_from_hex
from kivymd.color_definitions import hue, palette, text_colors
from kivymd.theming import ThemeManager
-from .elevation import CommonElevationBehavior
-
Builder.load_string(
"""
#:import RelativeLayout kivy.uix.relativelayout.RelativeLayout
@@ -38,7 +37,7 @@ Builder.load_string(
angle: self.angle
origin: self._background_origin
Color:
- rgba: self.md_bg_color
+ rgba: self._md_bg_color
RoundedRectangle:
group: "Background_instruction"
size: self.size
@@ -67,7 +66,7 @@ Builder.load_string(
)
-class BackgroundColorBehavior(CommonElevationBehavior):
+class BackgroundColorBehavior:
background = StringProperty()
"""
Background image path.
@@ -153,15 +152,26 @@ class BackgroundColorBehavior(CommonElevationBehavior):
_background_x = NumericProperty(0)
_background_y = NumericProperty(0)
- _background_origin = ReferenceListProperty(
- _background_x,
- _background_y,
- )
+ _background_origin = ReferenceListProperty(_background_x, _background_y)
+ _md_bg_color = ColorProperty([0, 0, 0, 0])
def __init__(self, **kwarg):
super().__init__(**kwarg)
self.bind(pos=self.update_background_origin)
+ def on_md_bg_color(self, instance_md_widget, color: Union[list, str]):
+ if (
+ hasattr(self, "theme_cls")
+ and self.theme_cls.theme_style_switch_animation
+ ):
+ Animation(
+ _md_bg_color=color,
+ d=self.theme_cls.theme_style_switch_animation_duration,
+ t="linear",
+ ).start(self)
+ else:
+ self._md_bg_color = color
+
def update_background_origin(
self, instance_md_widget, pos: List[float]
) -> None:
@@ -206,12 +216,14 @@ class SpecificBackgroundColorBehavior(BackgroundColorBehavior):
super().__init__(**kwargs)
if hasattr(self, "theme_cls"):
self.theme_cls.bind(
- primary_palette=self._update_specific_text_color
+ primary_palette=self._update_specific_text_color,
+ accent_palette=self._update_specific_text_color,
+ theme_style=self._update_specific_text_color,
)
- self.theme_cls.bind(accent_palette=self._update_specific_text_color)
- self.theme_cls.bind(theme_style=self._update_specific_text_color)
- self.bind(background_hue=self._update_specific_text_color)
- self.bind(background_palette=self._update_specific_text_color)
+ self.bind(
+ background_hue=self._update_specific_text_color,
+ background_palette=self._update_specific_text_color,
+ )
self._update_specific_text_color(None, None)
def _update_specific_text_color(
@@ -234,5 +246,17 @@ class SpecificBackgroundColorBehavior(BackgroundColorBehavior):
secondary_color[3] = 0.54
else:
secondary_color[3] = 0.7
- self.specific_text_color = color
- self.specific_secondary_text_color = secondary_color
+
+ if (
+ hasattr(self, "theme_cls")
+ and self.theme_cls.theme_style_switch_animation
+ ):
+ Animation(
+ specific_text_color=color,
+ specific_secondary_text_color=secondary_color,
+ d=self.theme_cls.theme_style_switch_animation_duration,
+ t="linear",
+ ).start(self)
+ else:
+ self.specific_text_color = color
+ self.specific_secondary_text_color = secondary_color
diff --git a/sbapp/kivymd/uix/behaviors/declarative_bahavior.py b/sbapp/kivymd/uix/behaviors/declarative_behavior.py
similarity index 100%
rename from sbapp/kivymd/uix/behaviors/declarative_bahavior.py
rename to sbapp/kivymd/uix/behaviors/declarative_behavior.py
diff --git a/sbapp/kivymd/uix/behaviors/elevation.py b/sbapp/kivymd/uix/behaviors/elevation.py
index 584c82b0..47b2af0a 100755
--- a/sbapp/kivymd/uix/behaviors/elevation.py
+++ b/sbapp/kivymd/uix/behaviors/elevation.py
@@ -11,405 +11,378 @@ Behaviors/Elevation
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/elevation-previous.png
:align: center
-There are 5 classes in KivyMD that can simulate shadow:
+To create an elevation effect, use the :class:`~CommonElevationBehavior` class.
+For example, let's create a button with a rectangular elevation effect:
- #. :class:`~FakeRectangularElevationBehavior`
- #. :class:`~FakeCircularElevationBehavior`
+.. tabs::
- #. :class:`~RectangularElevationBehavior`
- #. :class:`~CircularElevationBehavior`
- #. :class:`~RoundedRectangularElevationBehavior`
+ .. tab:: Declarative style with KV
-By default, KivyMD widgets use the elevation behavior implemented in classes
-:class:`~FakeRectangularElevationBehavior` and :class:`~FakeCircularElevationBehavior`
-for cast shadows. These classes use the old method of rendering shadows and it
-doesn't look very aesthetically pleasing. Shadows are harsh, no softness:
-
-The :class:`~RectangularElevationBehavior`, :class:`~CircularElevationBehavior`,
-:class:`~RoundedRectangularElevationBehavior` classes use the new shadow
-rendering algorithm, based on textures creation using the `Pillow` library.
-It looks very aesthetically pleasing and beautiful.
-
-.. warning:: Remember that :class:`~RectangularElevationBehavior`,
- :class:`~CircularElevationBehavior`, :class:`~RoundedRectangularElevationBehavior`
- classes require a lot of resources from the device on which your application will run,
- so you should not use these classes on mobile devices.
-
-.. code-block:: python
-
- from kivy.lang import Builder
- from kivy.uix.widget import Widget
-
- from kivymd.app import MDApp
- from kivymd.uix.card import MDCard
- from kivymd.uix.behaviors import RectangularElevationBehavior
- from kivymd.uix.boxlayout import MDBoxLayout
-
- KV = '''
- <Box@MDBoxLayout>
- adaptive_size: True
- orientation: "vertical"
- spacing: "36dp"
+ .. code-block:: python
+ from kivy.lang import Builder
+ from kivy.uix.behaviors import ButtonBehavior
- <BaseShadowWidget>
- size_hint: None, None
- size: 100, 100
- md_bg_color: 0, 0, 1, 1
- elevation: 36
- pos_hint: {'center_x': .5}
+ from kivymd.app import MDApp
+ from kivymd.uix.behaviors import (
+ RectangularRippleBehavior,
+ BackgroundColorBehavior,
+ CommonElevationBehavior,
+ )
+ KV = '''
+ <RectangularElevationButton>
+ size_hint: None, None
+ size: "250dp", "50dp"
- MDFloatLayout:
- MDBoxLayout:
- adaptive_size: True
- pos_hint: {'center_x': .5, 'center_y': .5}
- spacing: "56dp"
+ MDScreen:
- Box:
+ # With elevation effect
+ RectangularElevationButton:
+ pos_hint: {"center_x": .5, "center_y": .6}
+ elevation: 4.5
+ shadow_offset: 0, 6
- MDLabel:
- text: "Deprecated shadow rendering"
- adaptive_size: True
+ # Without elevation effect
+ RectangularElevationButton:
+ pos_hint: {"center_x": .5, "center_y": .4}
+ '''
- DeprecatedShadowWidget:
- MDLabel:
- text: "Doesn't require a lot of resources"
- adaptive_size: True
+ class RectangularElevationButton(
+ RectangularRippleBehavior,
+ CommonElevationBehavior,
+ ButtonBehavior,
+ BackgroundColorBehavior,
+ ):
+ def __init__(self, **kwargs):
+ super().__init__(**kwargs)
+ self.md_bg_color = "red"
- Box:
- MDLabel:
- text: "New shadow rendering"
- adaptive_size: True
+ class Example(MDApp):
+ def build(self):
+ return Builder.load_string(KV)
- NewShadowWidget:
- MDLabel:
- text: "It takes a lot of resources"
- adaptive_size: True
- '''
+ Example().run()
+ .. tab:: Declarative python style
- class BaseShadowWidget(Widget):
- pass
+ .. code-block:: python
+ from kivy.uix.behaviors import ButtonBehavior
- class DeprecatedShadowWidget(MDCard, BaseShadowWidget):
- '''Deprecated shadow rendering. Doesn't require a lot of resources.'''
+ from kivymd.app import MDApp
+ from kivymd.uix.behaviors import (
+ RectangularRippleBehavior,
+ BackgroundColorBehavior,
+ CommonElevationBehavior,
+ )
+ from kivymd.uix.screen import MDScreen
- class NewShadowWidget(RectangularElevationBehavior, BaseShadowWidget, MDBoxLayout):
- '''New shadow rendering. It takes a lot of resources.'''
+ class RectangularElevationButton(
+ RectangularRippleBehavior,
+ CommonElevationBehavior,
+ ButtonBehavior,
+ BackgroundColorBehavior,
+ ):
+ def __init__(self, **kwargs):
+ super().__init__(**kwargs)
+ self.md_bg_color = "red"
+ self.size_hint = (None, None)
+ self.size = ("250dp", "50dp")
- class Example(MDApp):
- def build(self):
- return Builder.load_string(KV)
+ class Example(MDApp):
+ def build(self):
+ return (
+ MDScreen(
+ RectangularElevationButton(
+ pos_hint={"center_x": .5, "center_y": .6},
+ elevation=4.5,
+ shadow_offset=(0, 6),
+ ),
+ RectangularElevationButton(
+ pos_hint={"center_x": .5, "center_y": .4},
+ ),
+ )
+ )
- Example().run()
+ Example().run()
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/elevation-differential.png
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/rectangular-elevation-effect.png
:align: center
+.. warning::
-For example, let's create an button with a rectangular elevation effect:
-
-.. code-block:: python
-
- from kivy.lang import Builder
- from kivy.uix.behaviors import ButtonBehavior
-
- from kivymd.app import MDApp
- from kivymd.uix.behaviors import (
- RectangularRippleBehavior,
- BackgroundColorBehavior,
- FakeRectangularElevationBehavior,
- )
-
- KV = '''
- <RectangularElevationButton>:
- size_hint: None, None
- size: "250dp", "50dp"
-
-
- MDScreen:
-
- # With elevation effect
- RectangularElevationButton:
- pos_hint: {"center_x": .5, "center_y": .6}
- elevation: 18
-
- # Without elevation effect
- RectangularElevationButton:
- pos_hint: {"center_x": .5, "center_y": .4}
- '''
+ If before the KivyMD 1.1.0 library version you used the elevation property
+ with an average value of `12` for the shadow, then starting with the KivyMD
+ 1.1.0 library version, the average value of the elevation property will be
+ somewhere `4`.
+Similarly, create a circular button:
- class RectangularElevationButton(
- RectangularRippleBehavior,
- FakeRectangularElevationBehavior,
- ButtonBehavior,
- BackgroundColorBehavior,
- ):
- md_bg_color = [0, 0, 1, 1]
+.. tabs::
+ .. tab:: Declarative style with KV
- class Example(MDApp):
- def build(self):
- return Builder.load_string(KV)
+ .. code-block:: python
+ from kivy.lang import Builder
+ from kivy.uix.behaviors import ButtonBehavior
- Example().run()
+ from kivymd.app import MDApp
+ from kivymd.uix.behaviors import CircularRippleBehavior, CommonElevationBehavior
+ from kivymd.uix.floatlayout import MDFloatLayout
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/rectangular-elevation-effect.gif
- :align: center
+ KV = '''
+ <CircularElevationButton>
+ size_hint: None, None
+ size: "100dp", "100dp"
+ radius: self.size[0] / 2
+ shadow_radius: self.radius[0]
+ md_bg_color: "red"
+
+ MDIcon:
+ icon: "hand-heart"
+ halign: "center"
+ valign: "center"
+ pos_hint: {"center_x": .5, "center_y": .5}
+ size: root.size
+ pos: root.pos
+ font_size: root.size[0] * .6
+ theme_text_color: "Custom"
+ text_color: "white"
+
+
+ MDScreen:
+
+ CircularElevationButton:
+ pos_hint: {"center_x": .5, "center_y": .6}
+ elevation: 4
+ '''
-Similarly, create a circular button:
-.. code-block:: python
+ class CircularElevationButton(
+ CommonElevationBehavior,
+ CircularRippleBehavior,
+ ButtonBehavior,
+ MDFloatLayout,
+ ):
+ pass
- from kivy.lang import Builder
- from kivy.uix.behaviors import ButtonBehavior
- from kivymd.uix.boxlayout import MDBoxLayout
- from kivymd.app import MDApp
- from kivymd.uix.behaviors import (
- CircularRippleBehavior,
- FakeCircularElevationBehavior,
- )
+ class Example(MDApp):
+ def build(self):
+ return Builder.load_string(KV)
- KV = '''
- <CircularElevationButton>:
- size_hint: None, None
- size: "100dp", "100dp"
- radius: self.size[0] / 2
- md_bg_color: 0, 0, 1, 1
- MDIcon:
- icon: "hand-heart"
- halign: "center"
- valign: "center"
- size: root.size
- pos: root.pos
- font_size: root.size[0] * .6
- theme_text_color: "Custom"
- text_color: [1] * 4
+ Example().run()
+ .. tab:: Declarative python style
- MDScreen:
+ .. code-block:: python
- CircularElevationButton:
- pos_hint: {"center_x": .5, "center_y": .6}
- elevation: 24
- '''
+ from kivy.metrics import dp
+ from kivy.uix.behaviors import ButtonBehavior
+ from kivymd.app import MDApp
+ from kivymd.uix.behaviors import CircularRippleBehavior, CommonElevationBehavior
+ from kivymd.uix.floatlayout import MDFloatLayout
+ from kivymd.uix.label import MDIcon
+ from kivymd.uix.screen import MDScreen
- class CircularElevationButton(
- FakeCircularElevationBehavior,
- CircularRippleBehavior,
- ButtonBehavior,
- MDBoxLayout,
- ):
- pass
+ class CircularElevationButton(
+ CommonElevationBehavior,
+ CircularRippleBehavior,
+ ButtonBehavior,
+ MDFloatLayout,
+ ):
+ def __init__(self, **kwargs):
+ super().__init__(**kwargs)
+ self.size_hint = (None, None)
+ self.size = (dp(100), dp(100))
+ self.radius = dp(100) / 2
+ self.shadow_radius = dp(100) / 2
+ self.md_bg_color = "red"
+ self.add_widget(
+ MDIcon(
+ icon="hand-heart",
+ halign="center",
+ valign="center",
+ pos_hint={"center_x": .5, "center_y": .5},
+ size=self.size,
+ theme_text_color="Custom",
+ text_color="white",
+ font_size=self.size[0] * 0.6,
+ )
+ )
- class Example(MDApp):
- def build(self):
- return Builder.load_string(KV)
+ class Example(MDApp):
+ def build(self):
+ return (
+ MDScreen(
+ CircularElevationButton(
+ pos_hint={"center_x": .5, "center_y": .5},
+ elevation=4,
+ )
+ )
+ )
- Example().run()
+ Example().run()
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/circular-fake-elevation.png
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/circular-elevation-effect.png
:align: center
Animating the elevation
-----------------------
-.. code-block:: python
+.. tabs::
- from kivy.animation import Animation
- from kivy.lang import Builder
- from kivy.properties import ObjectProperty
- from kivy.uix.behaviors import ButtonBehavior
+ .. tab:: Declarative style with KV
- from kivymd.app import MDApp
- from kivymd.theming import ThemableBehavior
- from kivymd.uix.behaviors import FakeRectangularElevationBehavior, RectangularRippleBehavior
- from kivymd.uix.boxlayout import MDBoxLayout
+ .. code-block:: python
- KV = '''
- MDFloatLayout:
+ from kivy.animation import Animation
+ from kivy.lang import Builder
+ from kivy.uix.behaviors import ButtonBehavior
- ElevatedWidget:
- pos_hint: {'center_x': .5, 'center_y': .5}
- size_hint: None, None
- size: 100, 100
- md_bg_color: 0, 0, 1, 1
- '''
+ from kivymd.app import MDApp
+ from kivymd.uix.behaviors import CommonElevationBehavior, RectangularRippleBehavior
+ from kivymd.uix.widget import MDWidget
+ KV = '''
+ MDScreen:
+
+ ElevatedWidget:
+ pos_hint: {'center_x': .5, 'center_y': .5}
+ size_hint: None, None
+ size: 100, 100
+ md_bg_color: 0, 0, 1, 1
+ elevation: 4
+ radius: 18
+ '''
- class ElevatedWidget(
- ThemableBehavior,
- FakeRectangularElevationBehavior,
- RectangularRippleBehavior,
- ButtonBehavior,
- MDBoxLayout,
- ):
- shadow_animation = ObjectProperty()
- def on_press(self, *args):
- if self.shadow_animation:
- Animation.cancel_all(self, "_elevation")
- self.shadow_animation = Animation(_elevation=self.elevation + 10, d=0.4)
- self.shadow_animation.start(self)
+ class ElevatedWidget(
+ CommonElevationBehavior,
+ RectangularRippleBehavior,
+ ButtonBehavior,
+ MDWidget,
+ ):
+ _elev = 0 # previous elevation value
- def on_release(self, *args):
- if self.shadow_animation:
- Animation.cancel_all(self, "_elevation")
- self.shadow_animation = Animation(_elevation=self.elevation, d=0.1)
- self.shadow_animation.start(self)
+ def on_press(self, *args):
+ if not self._elev:
+ self._elev = self.elevation
+ Animation(elevation=self.elevation + 2, d=0.4).start(self)
+ def on_release(self, *args):
+ Animation.cancel_all(self, "elevation")
+ Animation(elevation=self._elev, d=0.1).start(self)
- class Example(MDApp):
- def build(self):
- return Builder.load_string(KV)
+ class Example(MDApp):
+ def build(self):
+ return Builder.load_string(KV)
- Example().run()
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/rectangular-elevation-animation-effect.gif
- :align: center
-
-Lighting position
------------------
+ Example().run()
-.. code-block:: python
+ .. tab:: Declarative python style
- from kivy.lang import Builder
+ .. code-block:: python
- from kivymd.app import MDApp
- from kivymd.uix.card import MDCard
- from kivymd.uix.boxlayout import MDBoxLayout
- from kivymd.uix.behaviors import RectangularElevationBehavior
+ from kivy.animation import Animation
+ from kivy.uix.behaviors import ButtonBehavior
- KV = '''
- MDScreen:
+ from kivymd.app import MDApp
+ from kivymd.uix.behaviors import CommonElevationBehavior, RectangularRippleBehavior
+ from kivymd.uix.screen import MDScreen
+ from kivymd.uix.widget import MDWidget
- ShadowCard:
- pos_hint: {'center_x': .5, 'center_y': .5}
- size_hint: None, None
- size: 100, 100
- shadow_pos: -10 + slider.value, -10 + slider.value
- elevation: 24
- md_bg_color: 1, 1, 1, 1
- MDSlider:
- id: slider
- max: 20
- size_hint_x: .6
- pos_hint: {'center_x': .5, 'center_y': .3}
- '''
+ class ElevatedWidget(
+ CommonElevationBehavior,
+ RectangularRippleBehavior,
+ ButtonBehavior,
+ MDWidget,
+ ):
+ _elev = 0 # previous elevation value
+ def on_press(self, *args):
+ if not self._elev:
+ self._elev = self.elevation
+ Animation(elevation=self.elevation + 2, d=0.4).start(self)
- class ShadowCard(RectangularElevationBehavior, MDBoxLayout):
- pass
+ def on_release(self, *args):
+ Animation.cancel_all(self, "elevation")
+ Animation(elevation=self._elev, d=0.1).start(self)
- class Example(MDApp):
- def build(self):
- return Builder.load_string(KV)
+ class Example(MDApp):
+ def build(self):
+ return (
+ MDScreen(
+ ElevatedWidget(
+ pos_hint={'center_x': .5, 'center_y': .5},
+ size_hint=(None, None),
+ size=(100, 100),
+ md_bg_color="blue",
+ elevation=4,
+ radius=18,
+ )
+ )
+ )
- Example().run()
+ Example().run()
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/shadow-pos.gif
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/rectangular-elevation-animation-effect.gif
:align: center
"""
+from __future__ import annotations
+
__all__ = (
"CommonElevationBehavior",
"RectangularElevationBehavior",
"CircularElevationBehavior",
"RoundedRectangularElevationBehavior",
- "ObservableShadow",
"FakeRectangularElevationBehavior",
"FakeCircularElevationBehavior",
)
-from io import BytesIO
-from weakref import WeakMethod, ref
+import os
from kivy import Logger
from kivy.clock import Clock
-from kivy.core.image import Image as CoreImage
-from kivy.lang import Builder
-from kivy.metrics import dp
+from kivy.core.window import Window
+from kivy.graphics import RenderContext, RoundedRectangle
from kivy.properties import (
AliasProperty,
BooleanProperty,
BoundedNumericProperty,
+ ColorProperty,
ListProperty,
NumericProperty,
ObjectProperty,
- ReferenceListProperty,
- StringProperty,
VariableListProperty,
)
from kivy.uix.widget import Widget
-from PIL import Image, ImageDraw, ImageFilter
-
-from kivymd.app import MDApp
-Builder.load_string(
- """
-#:import InstructionGroup kivy.graphics.instructions.InstructionGroup
-
-
-<CommonElevationBehavior>
- canvas.before:
- # SOFT SHADOW
- PushMatrix
- Rotate:
- angle: self.angle
- origin: self._shadow_origin
- Color:
- group: "soft_shadow"
- rgba: root.soft_shadow_cl
- Rectangle:
- group: "soft_shadow"
- texture: self._soft_shadow_texture
- size: self.soft_shadow_size
- pos: self.soft_shadow_pos
- PopMatrix
-
- # HARD SHADOW
- PushMatrix
- Rotate:
- angle: self.angle
- origin: self.center
- Color:
- group: "hard_shadow"
- rgba: root.hard_shadow_cl
- Rectangle:
- group: "hard_shadow"
- texture: self.hard_shadow_texture
- size: self.hard_shadow_size
- pos: self.hard_shadow_pos
- PopMatrix
- Color:
- group: "shadow"
- a: 1
-""",
- filename="CommonElevationBehavior.kv",
-)
+from kivymd import glsl_path
+# FIXME: Add shadow manipulation with canvas instructions such as
+# PushMatrix and PopMatrix.
class CommonElevationBehavior(Widget):
"""Common base class for rectangular and circular elevation behavior."""
@@ -417,1059 +390,509 @@ class CommonElevationBehavior(Widget):
"""
Elevation of the widget.
- .. note::
- Although, this value does not represent the current elevation of the
- widget. :attr:`~CommonElevationBehavior._elevation` can be used to
- animate the current elevation and come back using the
- :attr:`~CommonElevationBehavior.elevation` property directly.
-
- For example:
-
- .. code-block:: python
-
- from kivy.lang import Builder
- from kivy.uix.behaviors import ButtonBehavior
-
- from kivymd.app import MDApp
- from kivymd.uix.behaviors import CircularElevationBehavior, CircularRippleBehavior
- from kivymd.uix.boxlayout import MDBoxLayout
-
- KV = '''
- #:import Animation kivy.animation.Animation
-
-
- <WidgetWithShadow>
- size_hint: [None, None]
- elevation: 6
- animation_: None
- md_bg_color: [1] * 4
- on_size:
- self.radius = [self.height / 2] * 4
- on_press:
- if self.animation_: \
- self.animation_.cancel(self); \
- self.animation_ = Animation(_elevation=self.elevation + 6, d=0.08); \
- self.animation_.start(self)
- on_release:
- if self.animation_: \
- self.animation_.cancel(self); \
- self.animation_ = Animation(_elevation = self.elevation, d=0.08); \
- self.animation_.start(self)
-
- MDFloatLayout:
-
- WidgetWithShadow:
- size: [root.size[1] / 2] * 2
- pos_hint: {"center": [0.5, 0.5]}
- '''
-
-
- class WidgetWithShadow(
- CircularElevationBehavior,
- CircularRippleBehavior,
- ButtonBehavior,
- MDBoxLayout,
- ):
- def __init__(self, **kwargs):
- # always set the elevation before the super().__init__ call
- # self.elevation = 6
- super().__init__(**kwargs)
-
- def on_size(self, *args):
- self.radius = [self.size[0] / 2]
-
-
- class Example(MDApp):
- def build(self):
- return Builder.load_string(KV)
-
-
- Example().run()
-
:attr:`elevation` is an :class:`~kivy.properties.BoundedNumericProperty`
and defaults to `0`.
"""
- # Shadow rendering properties.
- # Shadow rotation memory - SHARED ACROSS OTHER CLASSES.
- angle = NumericProperty(0)
+ shadow_radius = VariableListProperty([0], length=4)
"""
- Angle of rotation in degrees of the current shadow.
- This value is shared across different widgets.
-
- .. note::
- This value will affect both, hard and soft shadows.
- Each shadow has his own origin point that's computed every time the
- elevation changes.
-
- .. warning::
- Do not add `PushMatrix` inside the canvas before and add `PopMatrix`
- in the next layer, this will cause visual errors, because the stack
- used will clip the push and pop matrix already inside the canvas.before
- canvas layer.
+ Radius of the corners of the shadow.
- Incorrect:
+ .. versionadded:: 1.1.0
- .. code-block:: kv
+ You don't have to use this parameter.
+ The radius of the elevation effect is calculated automatically one way
+ or another based on the radius of the parent widget, for example:
- <TiltedWidget>
- canvas.before:
- PushMatrix
- [...]
- canvas:
- PopMatrix
+ .. code-block:: python
- Correct:
+ from kivy.lang import Builder
- .. code-block:: kv
+ from kivymd.app import MDApp
- <TiltedWidget>
- canvas.before:
- PushMatrix
- [...]
- PopMatrix
+ KV = '''
+ MDScreen:
+ MDCard:
+ radius: 12, 46, 12, 46
+ size_hint: .5, .3
+ pos_hint: {"center_x": .5, "center_y": .5}
+ elevation: 4
+ shadow_softness: 8
+ shadow_offset: (-2, 2)
+ '''
- :attr:`angle` is an :class:`~kivy.properties.NumericProperty`
- and defaults to `0`.
- """
+ class Test(MDApp):
+ def build(self):
+ return Builder.load_string(KV)
- radius = VariableListProperty([0])
- """
- Radius of the corners of the shadow.
- This values represents each corner of the shadow, starting from `top-left`
- corner and going clockwise.
- .. code-block:: python
+ Test().run()
- radius = [
- "top-left",
- "top-right",
- "bottom-right",
- "bottom-left",
- ]
+ .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/shadow-radius.png
+ :align: center
- This value can be expanded thus allowing this settings to be valid:
+ .. note::
+ However, if you want to use this parameter, remember that the angle
+ values for the radius of the Kivy widgets and the radius for the shader
+ are different.
.. code-block:: python
- widget.radius=[0] # Translates to [0, 0, 0, 0]
- widget.radius=[10, 3] # Translates to [10, 3, 10, 3]
- widget.radius=[7.0, 8.7, 1.5, 3.0] # Translates to [7, 8, 1, 3]
-
- .. note::
- This value will affect both, hard and soft shadows.
- This value only affects :class:`~RoundedRectangularElevationBehavior`
- for now, but can be stored and used by custom shadow draw functions.
+ shadow_radius = ['top-right', 'bot-right', 'top-left', 'bot-left']
+ kivy_radius = ['top-left', 'top-right', 'bottom-right', 'bottom-left']
- :attr:`radius` is an :class:`~kivy.properties.VariableListProperty`
+ :attr:`shadow_radius` is an :class:`~kivy.properties.VariableListProperty`
and defaults to `[0, 0, 0, 0]`.
"""
- # Position of the shadow.
- _shadow_origin_x = NumericProperty(0)
+ shadow_softness = NumericProperty(12)
"""
- Shadow origin `x` position for the rotation origin.
+ Softness of the shadow.
- Managed by `_shadow_origin`.
+ .. versionadded:: 1.1.0
- :attr:`_shadow_origin_x` is an :class:`~kivy.properties.NumericProperty`
- and defaults to `0`.
+ .. code-block:: python
- .. note::
- This property is automatically processed. by _shadow_origin.
- """
+ from kivy.lang import Builder
- _shadow_origin_y = NumericProperty(0)
- """
- Shadow origin y position for the rotation origin.
+ from kivymd.app import MDApp
+ from kivymd.uix.behaviors import BackgroundColorBehavior, CommonElevationBehavior
- Managed by :attr:`_shadow_origin`.
+ KV = '''
+ <RectangularElevationButton>
+ size_hint: None, None
+ size: "250dp", "50dp"
- :attr:`_shadow_origin_y` is an :class:`~kivy.properties.NumericProperty`
- and defaults to `0`.
- .. note::
- This property is automatically processed.
- """
+ MDScreen:
- _shadow_origin = ReferenceListProperty(_shadow_origin_x, _shadow_origin_y)
- """
- Soft shadow rotation origin point.
+ RectangularElevationButton:
+ pos_hint: {"center_x": .5, "center_y": .6}
+ elevation: 6
+ shadow_softness: 6
- :attr:`_shadow_origin` is an :class:`~kivy.properties.ReferenceListProperty`
- and defaults to `[0, 0]`.
+ RectangularElevationButton:
+ pos_hint: {"center_x": .5, "center_y": .4}
+ elevation: 6
+ shadow_softness: 12
+ '''
- .. note::
- This property is automatically processed and relative to the canvas center.
- """
- _shadow_pos = ListProperty([0, 0]) # custom offset
- """
- Soft shadow origin point.
+ class RectangularElevationButton(CommonElevationBehavior, BackgroundColorBehavior):
+ md_bg_color = [0, 0, 1, 1]
- :attr:`_shadow_pos` is an :class:`~kivy.properties.ListProperty`
- and defaults to `[0, 0]`.
- .. note::
- This property is automatically processed and relative to the widget's
- canvas center.
- """
+ class Example(MDApp):
+ def build(self):
+ return Builder.load_string(KV)
- shadow_pos = ListProperty([0, 0]) # bottom left corner
- """
- Custom shadow origin point. If this property is set, :attr:`_shadow_pos`
- will be ommited.
- This property allows users to fake light source.
+ Example().run()
- :attr:`shadow_pos` is an :class:`~kivy.properties.ListProperty`
- and defaults to `[0, 0]`.
+ .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/shadow-softness.png
+ :align: center
- .. note::
- this value overwrite the :attr:`_shadow_pos` processing.
+ :attr:`shadow_softness` is an :class:`~kivy.properties.NumericProperty`
+ and defaults to `12`.
"""
- # Shadow Group shared memory
- __shadow_groups = {"global": []}
-
- shadow_group = StringProperty("global")
+ shadow_offset = ListProperty((0, 2))
"""
- Widget's shadow group.
- By default every widget with a shadow is saved inside the memory
- :attr:`__shadow_groups` as a weakref. This means that you can have multiple
- light sources, one for every shadow group.
+ Offset of the shadow.
- To fake a light source use :attr:`force_shadow_pos`.
+ .. versionadded:: 1.1.0
- :attr:`shadow_group` is an :class:`~kivy.properties.StringProperty`
- and defaults to `"global"`.
- """
+ .. code-block:: python
- _elevation = BoundedNumericProperty(0, min=0, errorvalue=0)
- """
- Current elevation of the widget.
+ from kivy.lang import Builder
- .. warning::
- This property is the current elevation of the widget, do not
- use this property directly, instead, use :class:`~CommonElevationBehavior`
- elevation.
+ from kivymd.app import MDApp
+ from kivymd.uix.behaviors import BackgroundColorBehavior, CommonElevationBehavior
- :attr:`_elevation` is an :class:`~kivy.properties.NumericProperty`
- and defaults to `0`.
- """
+ KV = '''
+ <RectangularElevationButton>
+ size_hint: None, None
+ size: "100dp", "100dp"
- # soft shadow
- _soft_shadow_texture = ObjectProperty()
- """
- Texture of the soft shadow texture for the canvas.
- :attr:`_soft_shadow_texture` is an :class:`~kivy.core.image.Image`
- and defaults to `None`.
+ MDScreen:
- .. note::
- This property is automatically processed.
- """
+ RectangularElevationButton:
+ pos_hint: {"center_x": .5, "center_y": .5}
+ elevation: 6
+ shadow_radius: 18
+ shadow_softness: 24
+ shadow_offset: 12, 12
+ '''
- soft_shadow_size = ListProperty([0, 0])
- """
- Size of the soft shadow texture over the canvas.
- :attr:`soft_shadow_size` is an :class:`~kivy.properties.ListProperty`
- and defaults to `[0, 0]`.
+ class RectangularElevationButton(CommonElevationBehavior, BackgroundColorBehavior):
+ md_bg_color = [0, 0, 1, 1]
- .. note::
- This property is automatically processed.
- """
- soft_shadow_pos = ListProperty([0, 0])
- """
- Position of the hard shadow texture over the canvas.
+ class Example(MDApp):
+ def build(self):
+ return Builder.load_string(KV)
- :attr:`soft_shadow_pos` is an :class:`~kivy.properties.ListProperty`
- and defaults to `[0, 0]`.
- .. note::
- This property is automatically processed.
- """
+ Example().run()
- soft_shadow_cl = ListProperty([0, 0, 0, 0.50])
- """
- Color of the soft shadow.
+ .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/shadow-offset-1.png
+ :align: center
- :attr:`soft_shadow_cl` is an :class:`~kivy.properties.ListProperty`
- and defaults to `[0, 0, 0, 0.15]`.
- """
+ .. code-block:: kv
- # hard shadow
- hard_shadow_texture = ObjectProperty()
- """
- Texture of the hard shadow texture for the canvas.
+ RectangularElevationButton:
+ shadow_offset: -12, 12
- :attr:`hard_shadow_texture` is an :class:`~kivy.core.image.Image`
- and defaults to `None`.
+ .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/shadow-offset-2.png
+ :align: center
- .. note::
- This property is automatically processed when elevation is changed.
- """
+ .. code-block:: kv
- hard_shadow_size = ListProperty([0, 0])
- """
- Size of the hard shadow texture over the canvas.
+ RectangularElevationButton:
+ shadow_offset: -12, -12
- :attr:`hard_shadow_size` is an :class:`~kivy.properties.ListProperty`
- and defaults to `[0, 0]`.
+ .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/shadow-offset-3.png
+ :align: center
- .. note::
- This property is automatically processed when elevation is changed.
- """
+ .. code-block:: kv
- hard_shadow_pos = ListProperty([0, 0])
- """
- Position of the hard shadow texture over the canvas.
+ RectangularElevationButton:
+ shadow_offset: 12, -12
- :attr:`hard_shadow_pos` is an :class:`~kivy.properties.ListProperty`
- and defaults to `[0, 0]`.
+ .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/shadow-offset-4.png
+ :align: center
- .. note::
- This property is automatically processed when elevation is changed.
+ :attr:`shadow_offset` is an :class:`~kivy.properties.ListProperty`
+ and defaults to `(0, 2)`.
"""
- hard_shadow_cl = ListProperty([0, 0, 0, 0.15])
+ shadow_color = ColorProperty([0, 0, 0, 0.6])
"""
- Color of the hard shadow.
+ Offset of the shadow.
- .. note::
- :attr:`hard_shadow_cl` is an :class:`~kivy.properties.ListProperty`
- and defaults to `[0, 0, 0, 0.15]`.
- """
+ .. versionadded:: 1.1.0
- # Shared property for some calculations.
- # This values are used to improve the gaussain blur and avoid that
- # the blur goes outside the texture.
- hard_shadow_offset = BoundedNumericProperty(
- 2, min=0, errorhandler=lambda x: 0 if x < 0 else x
- )
- """
- This value sets a special offset to the shadow canvas, this offset allows a
- correct draw of the canvas size. allowing the effect to correctly blur the
- image in the given space.
+ .. code-block:: python
- :attr:`hard_shadow_offset` is an :class:`~kivy.properties.BoundedNumericProperty`
- and defaults to `2`.
- """
+ RectangularElevationButton:
+ shadow_color: 0, 0, 1, .8
- soft_shadow_offset = BoundedNumericProperty(
- 4, min=0, errorhandler=lambda x: 0 if x < 0 else x
- )
- """
- This value sets a special offset to the shadow canvas, this offset allows a
- correct draw of the canvas size. allowing the effect to correctly blur the
- image in the given space.
+ .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/shadow-color.png
+ :align: center
- :attr:`soft_shadow_offset` is an :class:`~kivy.properties.BoundedNumericProperty`
- and defaults to `4`.
+ :attr:`shadow_color` is an :class:`~kivy.properties.ColorProperty`
+ and defaults to `[0.4, 0.4, 0.4, 0.8]`.
"""
- draw_shadow = ObjectProperty(None)
- """
- This property controls the draw call of the context.
+ _transition_ref = ObjectProperty()
+ _has_relative_position = BooleanProperty(defaultvalue=False)
+ _elevation = 0
+ _shadow_color = [0.0, 0.0, 0.0, 0.0]
- This property is automatically set to :attr:`__draw_shadow__` inside the
- `super().__init__ call.` unless the property is different of None.
+ def _get_window_pos(self, *args):
+ window_pos = self.to_window(*self.pos)
+ # To list, so it can be compared to self.pos directly.
+ return [window_pos[0], window_pos[1]]
- To set a different drawing instruction function, set this property before the
- `super(),__init__` call inside the `__init__` definition of the new class.
+ def _set_window_pos(self, value):
+ self.window_pos = value
- You can use the source for this classes as example of how to draw over
- with the context:
+ window_pos = AliasProperty(_get_window_pos, _set_window_pos)
- Real time shadows:
- #. :class:`~RectangularElevationBehavior`
- #. :class:`~CircularElevationBehavior`
- #. :class:`~RoundedRectangularElevationBehavior`
- #. :class:`~ObservableShadow`
+ def __init__(self, **kwargs):
+ super().__init__(**kwargs)
+ with self.canvas.before:
+ self.context = RenderContext(use_parent_projection=True)
+ with self.context:
+ self.rect = RoundedRectangle(pos=self.pos, size=self.size)
- Fake shadows (d`ont use this property):
- #. :class:`~FakeRectangularElevationBehavior`
- #. :class:`~FakeCircularElevationBehavior`
+ self.after_init()
- :attr:`draw_shadow` is an :class:`~kivy.properties.ObjectProperty`
- and defaults to `None`.
+ def after_init(self, *args):
+ Clock.schedule_once(self.check_for_relative_behavior)
+ Clock.schedule_once(self.set_shader_string)
+ Clock.schedule_once(lambda x: self.on_elevation(self, self.elevation))
+ self.on_pos()
+
+ def check_for_relative_behavior(self, *args) -> None:
+ """
+ Checks if the widget has relative properties and if necessary
+ binds Window.on_draw and screen events to fix behavior
+ """
- .. note:: If this property is left to `None` the
- :class:`~CommonElevationBehavior` will set to a function that will
- raise a `NotImplementedError` inside `super().__init__`.
+ if self.pos != self.window_pos:
+ self._has_relative_position = True
+
+ # Loops to check if its inside screenmanager or bottom_navigation.
+ widget = self
+ while True:
+ # Checks if has screen event function
+ # works for Screen and MDTab objects.
+ if hasattr(widget, "on_pre_enter"):
+ widget.bind(on_pre_enter=self.apply_correction)
+ widget.bind(on_pre_leave=self.apply_correction)
+ widget.bind(on_enter=self.reset_correction)
+ widget.bind(on_leave=self.reset_correction)
+ self._has_relative_position = True
+
+ # Save refs to objects with transition property.
+ if hasattr(widget, "header"): # specific to bottom_nav
+ self._transition_ref = widget.header.panel
+ elif hasattr(widget, "manager"): # specific to screen
+ if widget.manager: # manager cant be None
+ self._transition_ref = widget.manager
+ break
- Follow the next example to set a new draw instruction for the class
- inside `__init__`:
+ elif widget.parent and str(widget) != str(widget.parent):
+ widget = widget.parent
+ else:
+ break
- .. code-block:: python
+ if self._has_relative_position:
+ Window.bind(on_draw=self.update_window_position)
+
+ def apply_correction(self, *args):
+ if self._transition_ref:
+ transition = str(self._transition_ref.transition)
+ # Slide and Card transitions only need _has_relative_pos to be
+ # always on.
+ if (
+ "SlideTransition" in transition
+ or "CardTransition" in transition
+ ):
+ self.context.use_parent_modelview = False
+ else:
+ self.context.use_parent_modelview = True
+
+ def reset_correction(self, *args):
+ self.context.use_parent_modelview = False
+ self.update_window_position()
+
+ def get_shader_string(self) -> str:
+ shader_string = ""
+ for name_file in ["header.frag", "elevation.frag", "main.frag"]:
+ with open(
+ os.path.join(glsl_path, "elevation", name_file),
+ encoding="utf-8",
+ ) as file:
+ shader_string += f"{file.read()}\n\n"
+
+ return shader_string
+
+ def set_shader_string(self, *args) -> None:
+ self.context["shadow_radius"] = list(map(float, self.shadow_radius))
+ self.context["shadow_softness"] = float(self.shadow_softness)
+ self.context["shadow_color"] = list(map(float, self.shadow_color))[
+ :-1
+ ] + [float(self.opacity)]
+ self.context["pos"] = list(map(float, self.rect.pos))
+ self.context.shader.fs = self.get_shader_string()
+
+ def update_resolution(self) -> None:
+ self.context["resolution"] = (*self.rect.size, *self.rect.pos)
+
+ def on_shadow_color(self, instance, value) -> None:
+ def on_shadow_color(*args):
+ self._shadow_color = list(map(float, value))[:-1] + [
+ float(self.opacity) if not self.disabled else 0
+ ]
+ self.context["shadow_color"] = self._shadow_color
- class RoundedRectangularElevationBehavior(CommonElevationBehavior):
- '''
- Shadow class for the RoundedRectangular shadow behavior.
- Controls the size and position of the shadow.
- '''
+ Clock.schedule_once(on_shadow_color)
- def __init__(self, **kwargs):
- self._draw_shadow = WeakMethod(self.__draw_shadow__)
- super().__init__(**kwargs)
+ def on_shadow_radius(self, instance, value) -> None:
+ def on_shadow_radius(*args):
+ if hasattr(self, "context"):
+ self.context["shadow_radius"] = list(map(float, value))
- def __draw_shadow__(self, origin, end, context=None):
- context.draw(...)
+ Clock.schedule_once(on_shadow_radius)
- Context is a `Pillow` `ImageDraw` class. For more information check the
- [Pillow official documentation](https://github.com/python-pillow/Pillow/).
- """
+ def on_shadow_softness(self, instance, value) -> None:
+ def on_shadow_softness(*args):
+ if hasattr(self, "context"):
+ self.context["shadow_softness"] = float(value)
- # All classes that uses a fake shadow shall set this value as `True`
- # for performance.
- _fake_elevation = BooleanProperty(False)
+ Clock.schedule_once(on_shadow_softness)
- def __init__(self, **kwargs):
- if self.draw_shadow is None:
- self.draw_shadow = WeakMethod(self.__draw_shadow__)
- self.prev_shadow_group = None
- im = BytesIO()
- Image.new("RGBA", (4, 4), color=(0, 0, 0, 0)).save(im, format="png")
- im.seek(0)
- # Setting a empty image as texture, improves performance.
- self._soft_shadow_texture = self.hard_shadow_texture = CoreImage(
- im, ext="png"
- ).texture
- Clock.schedule_once(self.shadow_preset, -1)
- self.on_shadow_group(self, self.shadow_group)
-
- self.bind(
- pos=self._update_shadow,
- size=self._update_shadow,
- radius=self._update_shadow,
- )
- super().__init__(**kwargs)
+ def on_elevation(self, instance, value) -> None:
+ def on_elevation(*args):
+ if hasattr(self, "context"):
+ self._elevation = value
+ self.hide_elevation(
+ True if (value <= 0 or self.disabled) else False
+ )
- def on_shadow_group(self, instance, value):
- """
- This function controls the shadow group of the widget.
- Do not use Directly to change the group. instead, use the shadow_group
- :attr:`property`.
- """
+ Clock.schedule_once(on_elevation)
- groups = CommonElevationBehavior.__shadow_groups
- if self.prev_shadow_group:
- group = groups[self.prev_shadow_group]
- for widget in group[:]:
- if widget() is self:
- group.remove(widget)
- group = self.prev_shadow_group = self.shadow_group
- if group not in groups:
- groups[group] = []
- r = ref(self, CommonElevationBehavior._clear_shadow_groups)
- groups[group].append(r)
-
- @staticmethod
- def _clear_shadow_groups(wk):
- # auto flush the element when the weak reference have been deleted
- groups = CommonElevationBehavior.__shadow_groups
- for group in list(groups.values()):
- if not group:
- break
- if wk in group:
- group.remove(wk)
- break
+ def on_shadow_offset(self, instance, value) -> None:
+ self.on_size()
+ self.on_pos()
- def force_shadow_pos(self, shadow_pos):
+ def update_window_position(self, *args) -> None:
"""
- This property forces the shadow position in every widget inside the
- widget. The argument :attr:`shadow_pos` is expected as a <class 'list'>
- or <class 'tuple'>.
+ This function is used only when the widget has relative position
+ properties.
"""
- if self.shadow_group is None:
- return
- group = CommonElevationBehavior.__shadow_groups[self.shadow_group]
- for wk in group[:]:
- widget = wk()
- if widget is None:
- group.remove(wk)
- widget.shadow_pos = shadow_pos
- del group
-
- def update_group_property(self, property_name, value):
- """
- This functions allows to change properties of every widget inside the
- shadow group.
- """
+ self.on_pos()
- if self.shadow_group is None:
+ def on_pos(self, *args) -> None:
+ if not hasattr(self, "rect"):
return
- group = CommonElevationBehavior.__shadow_groups[self.shadow_group]
- for wk in group[:]:
- widget = wk()
- if widget is None:
- group.remove(wk)
- setattr(widget, property_name, value)
- del group
-
- def shadow_preset(self, *args):
- """
- This function is meant to set the default configuration of the
- elevation.
- After a new instance is created, the elevation property will be launched
- and thus this function will update the elevation if the KV lang have not
- done it already.
+ if (
+ self._has_relative_position
+ and not self.context.use_parent_modelview
+ ):
+ pos = self.window_pos
+ else:
+ pos = self.pos
+
+ self.rect.pos = [
+ pos[0]
+ - ((self.rect.size[0] - self.width) / 2)
+ - self.shadow_offset[0],
+ pos[1]
+ - ((self.rect.size[1] - self.height) / 2)
+ - self.shadow_offset[1],
+ ]
- Works similar to an `__after_init__` call inside a widget.
- """
+ self.context["mouse"] = [self.rect.pos[0], 0.0, 0.0, 0.0]
+ self.context["pos"] = list(map(float, self.rect.pos))
+ self.update_resolution()
- from kivymd.uix.card import MDCard
+ def on_size(self, *args) -> None:
+ if not hasattr(self, "rect"):
+ return
- if self.elevation is None and not issubclass(self.__class__, MDCard):
- self.elevation = 10
- if self._fake_elevation is False:
- self._update_shadow(self, self.elevation)
- self.bind(
- pos=self._update_shadow,
- size=self._update_shadow,
- _elevation=self._update_shadow,
+ # If the elevation value is 0, set the canvas size to zero.
+ # Because even with a zero elevation value, the shadow is displayed
+ # under the widget. This is visible if we change the scale
+ # of the widget.
+ width = self.size[0] if self.elevation else 0
+ height = self.size[1] if self.elevation else 0
+ self.rect.size = (
+ width + (self._elevation * self.shadow_softness / 2),
+ height + (self._elevation * self.shadow_softness / 2),
)
- def on_elevation(self, instance, value):
- """
- Elevation event that sets the current elevation value to `_elevation`.
- """
-
- if value is not None:
- self._elevation = value
+ self.context["mouse"] = [self.rect.pos[0], 0.0, 0.0, 0.0]
+ self.context["size"] = list(map(float, self.rect.size))
+ self.update_resolution()
- def _set_soft_shadow_a(self, value):
- value = 0 if value < 0 else (1 if value > 1 else value)
- self.soft_shadow_cl[-1] = value
- return True
-
- def _set_hard_shadow_a(self, value):
- value = 0 if value < 0 else (1 if value > 1 else value)
- self.hard_shadow_cl[-1] = value
- return True
-
- def _get_soft_shadow_a(self):
- return self.soft_shadow_cl[-1]
-
- def _get_hard_shadow_a(self):
- return self.hard_shadow_cl[-1]
-
- _soft_shadow_a = AliasProperty(
- _get_soft_shadow_a, _set_soft_shadow_a, bind=["soft_shadow_cl"]
- )
- _hard_shadow_a = AliasProperty(
- _get_hard_shadow_a, _set_hard_shadow_a, bind=["hard_shadow_cl"]
- )
-
- def on_disabled(self, instance, value):
+ def on_opacity(self, instance, value: int | float) -> None:
"""
- This function hides the shadow when the widget is disabled.
- It sets the shadow to `0`.
+ Adjusts the transparency of the shadow according to the transparency
+ of the widget.
"""
- if self.disabled is True:
- self._elevation = 0
- else:
- self._elevation = 0 if self.elevation is None else self.elevation
- self._update_shadow(self, self._elevation)
- try:
- super().on_disabled(instance, value)
- except Exception:
- pass
-
- def _update_elevation(self, instance, value):
- self._elevation = value
- self._update_shadow(instance, value)
-
- def _update_shadow_pos(self, instance, value):
- if self._elevation > 0:
- self.hard_shadow_pos = [
- self.x - dp(self.hard_shadow_offset), # + self.shadow_pos[0],
- self.y - dp(self.hard_shadow_offset), # + self.shadow_pos[1],
+ def on_opacity(*args):
+ self._shadow_color = list(map(float, self._shadow_color))[:-1] + [
+ float(value)
]
- if self.shadow_pos == [0, 0]:
- self.soft_shadow_pos = [
- self.x
- + self._shadow_pos[0]
- - self._elevation
- - dp(self.soft_shadow_offset),
- self.y
- + self._shadow_pos[1]
- - self._elevation
- - dp(self.soft_shadow_offset),
- ]
- else:
- self.soft_shadow_pos = [
- self.x
- + self.shadow_pos[0]
- - self._elevation
- - dp(self.soft_shadow_offset),
- self.y
- + self.shadow_pos[1]
- - self._elevation
- - dp(self.soft_shadow_offset),
- ]
- self._shadow_origin = [
- self.soft_shadow_pos[0] + self.soft_shadow_size[0] / 2,
- self.soft_shadow_pos[1] + self.soft_shadow_size[1] / 2,
- ]
-
- def on__shadow_pos(self, ins, val):
- """
- Updates the shadow with the computed value.
-
- Call this function every time you need to force a shadow update.
- """
+ self.context["shadow_color"] = self._shadow_color
- self._update_shadow_pos(ins, val)
+ super().on_opacity(instance, value)
+ Clock.schedule_once(on_opacity)
- def on_shadow_pos(self, ins, val):
- """
- Updates the shadow with the fixed value.
-
- Call this function every time you need to force a shadow update.
- """
+ def on_radius(self, instance, value) -> None:
+ self.shadow_radius = [value[1], value[2], value[0], value[3]]
- self._update_shadow_pos(ins, val)
-
- def _update_shadow(self, instance, value):
- self._update_shadow_pos(instance, value)
- if self._elevation > 0 and self._fake_elevation is False:
- # dynamic elevation position for the shadow
- if self.shadow_pos == [0, 0]:
- self._shadow_pos = [0, -self._elevation * 0.4]
-
- # HARD Shadow
- offset = int(dp(self.hard_shadow_offset))
- size = [
- int(self.size[0] + (offset * 2)),
- int(self.size[1] + (offset * 2)),
- ]
- im = BytesIO()
- # context
- img = Image.new("RGBA", tuple(size), color=(0, 0, 0, 0))
- # draw context
- shadow = ImageDraw.Draw(img)
- self.draw_shadow()(
- [offset, offset],
- [
- int(size[0] - 1 - offset),
- int(size[1] - 1 - offset),
- ],
- context=shadow
- # context=ref(shadow)
- )
- img = img.filter(
- ImageFilter.GaussianBlur(
- radius=int(dp(1 + self.hard_shadow_offset / 3))
- )
- )
- img.save(im, format="png")
- im.seek(0)
- self.hard_shadow_size = size
- self.hard_shadow_texture = CoreImage(im, ext="png").texture
-
- # soft shadow
- if self.soft_shadow_cl[-1] > 0:
- offset = dp(self.soft_shadow_offset)
- size = [
- int(self.size[0] + dp(self._elevation * 2) + (offset * 2)),
- int(self.size[1] + dp(self._elevation * 2) + (offset * 2)),
- # ((self._elevation)*2) + x + (offset*2)) for x in self.size
- ]
- im = BytesIO()
- img = Image.new("RGBA", tuple(size), color=((0,) * 4))
- shadow = ImageDraw.Draw(img)
- _offset = int(dp(self._elevation + offset))
- self.draw_shadow()(
- [
- _offset,
- _offset,
- ],
- [int(size[0] - _offset - 1), int(size[1] - _offset - 1)],
- context=shadow
- # context=ref(shadow)
- )
- img = img.filter(
- ImageFilter.GaussianBlur(radius=self._elevation // 2)
- )
- shadow = ImageDraw.Draw(img)
- img.save(im, format="png")
- im.seek(0)
- self.soft_shadow_size = size
- self._soft_shadow_texture = CoreImage(im, ext="png").texture
+ def on_disabled(self, instance, value) -> None:
+ if value:
+ self._elevation = 0
+ self.hide_elevation(True)
else:
- im = BytesIO()
- Image.new("RGBA", (4, 4), color=(0, 0, 0, 0)).save(im, format="png")
- im.seek(0)
- self._soft_shadow_texture = self.hard_shadow_texture = CoreImage(
- im, ext="png"
- ).texture
- return
+ self.hide_elevation(False)
- def _get_center(self):
- center = [self.pos[0] + self.width / 2, self.pos[1] + self.height / 2]
- return center
+ def hide_elevation(self, hide: bool) -> None:
+ if hide:
+ self._elevation = -self.elevation
+ self._shadow_color = [0.0, 0.0, 0.0, 0.0]
+ else:
+ self._elevation = self.elevation
+ self._shadow_color = self.shadow_color[:-1] + [float(self.opacity)]
- def __draw_shadow__(self, origin, end, context=None):
- Logger.warning(
- f"KivyMD: "
- f"If you see this error, this means that either youre using "
- f"`CommonElevationBehavio`r directly or your 'shader' dont have a "
- f"`_draw_shadow` instruction, remember to overwrite this function"
- f"to draw over the image context. Тhe figure you would like. "
- f"Or your class {self.__class__.__name__} is not inherited from "
- f"any of the classes {__all__}"
- )
+ self.on_shadow_color(self, self._shadow_color)
+ self.on_size()
+ self.on_pos()
class RectangularElevationBehavior(CommonElevationBehavior):
"""
- Base class for a rectangular elevation behavior.
+ .. deprecated:: 1.1.0
+ Use :class:`~CommonElevationBehavior` class instead.
"""
def __init__(self, **kwargs):
- self.draw_shadow = WeakMethod(self.__draw_shadow__)
super().__init__(**kwargs)
-
- def __draw_shadow__(self, origin, end, context=None):
- context.rectangle(origin + end, fill=tuple([255] * 4))
+ Logger.warning(
+ "KivyMD: "
+ "The `RectangularElevationBehavior` class has been deprecated. "
+ "Use the `CommonElevationBehavior` class instead.`"
+ )
class CircularElevationBehavior(CommonElevationBehavior):
"""
- Base class for a circular elevation behavior.
+ .. deprecated:: 1.1.0
+ Use :class:`~CommonElevationBehavior` class instead.
"""
def __init__(self, **kwargs):
- self.draw_shadow = WeakMethod(self.__draw_shadow__)
super().__init__(**kwargs)
-
- def __draw_shadow__(self, origin, end, context=None):
- context.ellipse(origin + end, fill=tuple([255] * 4))
+ Logger.warning(
+ "KivyMD: "
+ "The `CircularElevationBehavior` class has been deprecated. "
+ "Use the `CommonElevationBehavior` class instead.`"
+ )
class RoundedRectangularElevationBehavior(CommonElevationBehavior):
"""
- Base class for rounded rectangular elevation behavior.
+ .. deprecated:: 1.1.0
+ Use :class:`~CommonElevationBehavior` class instead.
"""
def __init__(self, **kwargs):
- self.bind(
- radius=self._update_shadow,
- )
- self.draw_shadow = WeakMethod(self.__draw_shadow__)
super().__init__(**kwargs)
-
- def __draw_shadow__(self, origin, end, context=None):
- if self.radius == [0, 0, 0, 0]:
- context.rectangle(origin + end, fill=tuple([255] * 4))
- else:
- radius = [x * 2 for x in self.radius]
- context.pieslice(
- [
- origin[0],
- origin[1],
- origin[0] + radius[0],
- origin[1] + radius[0],
- ],
- 180,
- 270,
- fill=(255, 255, 255, 255),
- )
- context.pieslice(
- [
- end[0] - radius[1],
- origin[1],
- end[0],
- origin[1] + radius[1],
- ],
- 270,
- 360,
- fill=(255, 255, 255, 255),
- )
- context.pieslice(
- [
- end[0] - radius[2],
- end[1] - radius[2],
- end[0],
- end[1],
- ],
- 0,
- 90,
- fill=(255, 255, 255, 255),
- )
- context.pieslice(
- [
- origin[0],
- end[1] - radius[3],
- origin[0] + radius[3],
- end[1],
- ],
- 90,
- 180,
- fill=(255, 255, 255, 255),
- )
- if all((x == self.radius[0] for x in self.radius)):
- radius = int(self.radius[0])
- context.rectangle(
- [
- origin[0] + radius,
- origin[1],
- end[0] - radius,
- end[1],
- ],
- fill=(255,) * 4,
- )
- context.rectangle(
- [
- origin[0],
- origin[1] + radius,
- end[0],
- end[1] - radius,
- ],
- fill=(255,) * 4,
- )
- else:
- radius = [
- max((self.radius[0], self.radius[1])),
- max((self.radius[1], self.radius[2])),
- max((self.radius[2], self.radius[3])),
- max((self.radius[3], self.radius[0])),
- ]
- context.rectangle(
- [
- origin[0] + self.radius[0],
- origin[1],
- end[0] - self.radius[1],
- end[1] - radius[2],
- ],
- fill=(255,) * 4,
- )
- context.rectangle(
- [
- origin[0] + radius[3],
- origin[1] + self.radius[1],
- end[0],
- end[1] - self.radius[2],
- ],
- fill=(255,) * 4,
- )
- context.rectangle(
- [
- origin[0] + self.radius[3],
- origin[1] + radius[0],
- end[0] - self.radius[2],
- end[1],
- ],
- fill=(255,) * 4,
- )
- context.rectangle(
- [
- origin[0],
- origin[1] + self.radius[0],
- end[0] - radius[2],
- end[1] - self.radius[3],
- ],
- fill=(255,) * 4,
- )
-
-
-class ObservableShadow(CommonElevationBehavior):
- """
- ObservableShadow is real time shadow render that it's intended to only
- render a partial shadow of widgets based upon on the window observable
- area, this is meant to improve the performance of bigger widgets.
-
- .. warning::
- This is an empty class, the name has been reserved for future use.
- if you include this clas in your object, you wil get a
- `NotImplementedError`.
- """
-
- def __init__(self, **kwargs):
- # self._shadow = MDApp.get_running_app().theme_cls.round_shadow
- # self._fake_elevation=True
- raise NotImplementedError(
- "ObservableShadow:\n\t" "This class is in current development"
+ Logger.warning(
+ "KivyMD: "
+ "The `RoundedRectangularElevationBehavior` class has been "
+ "deprecated. Use the `CommonElevationBehavior` class instead.`"
)
- super().__init__(**kwargs)
class FakeRectangularElevationBehavior(CommonElevationBehavior):
"""
- `FakeRectangularElevationBehavio`r is a shadow mockup for widgets. Improves
- performance using cached images inside `kivymd.images` dir
-
- This class cast a fake Rectangular shadow behaind the widget.
-
- You can either use this behavior to overwrite the elevation of a prefab
- widget, or use it directly inside a new widget class definition.
-
- Use this class as follows for new widgets:
-
- .. code-block:: python
-
- class NewWidget(
- ThemableBehavior,
- FakeCircularElevationBehavior,
- SpecificBackgroundColorBehavior,
- # here you add the other front end classes for the widget front_end,
- ):
- [...]
-
- With this method each class can draw it's content in the canvas in the
- correct order, avoiding some visual errors.
-
- `FakeCircularElevationBehavior` will load prefabricated textures to
- optimize loading times.
-
- .. note:: About rounded corners:
- be careful, since this behavior is a mockup and will not draw any
- rounded corners.
+ .. deprecated:: 1.1.0
+ Use :class:`~CommonElevationBehavior` class instead.
"""
def __init__(self, **kwargs):
- # self._shadow = MDApp.get_running_app().theme_cls.round_shadow
- self.draw_shadow = WeakMethod(self.__draw_shadow__)
- self._fake_elevation = True
- self._update_shadow(self, self.elevation)
super().__init__(**kwargs)
-
- def _update_shadow(self, *args):
- if self._elevation > 0:
- # Set shadow size.
- ratio = self.width / (self.height if self.height != 0 else 1)
- if -2 < ratio < 2:
- self._shadow = MDApp.get_running_app().theme_cls.quad_shadow
- width = soft_width = self.width * 1.9
- height = soft_height = self.height * 1.9
- elif ratio <= -2:
- self._shadow = MDApp.get_running_app().theme_cls.rec_st_shadow
- ratio = abs(ratio)
- if ratio > 5:
- ratio = ratio * 22
- else:
- ratio = ratio * 11.5
- width = soft_width = self.width * 1.9
- height = self.height + dp(ratio)
- soft_height = (
- self.height + dp(ratio) + dp(self._elevation) * 0.5
- )
- else:
- self._shadow = MDApp.get_running_app().theme_cls.quad_shadow
- width = soft_width = self.width * 1.8
- height = soft_height = self.height * 1.8
-
- self.soft_shadow_size = (soft_width, soft_height)
- self.hard_shadow_size = (width, height)
- # Set ``soft_shadow`` parameters.
- center_x, center_y = self._get_center()
- self.hard_shadow_pos = self.soft_shadow_pos = (
- center_x - soft_width / 2,
- center_y - soft_height / 2 - dp(self._elevation * 0.5),
- )
- # Set transparency
- self._soft_shadow_a = 0.1 * 1.05**self._elevation
- self._hard_shadow_a = 0.4 * 0.8**self._elevation
- t = int(round(self._elevation))
- if 0 < t <= 23:
- self._soft_shadow_texture = (
- self._hard_shadow_texture
- ) = self._shadow.textures[str(t)]
- else:
- self._soft_shadow_texture = (
- self._hard_shadow_texture
- ) = self._shadow.textures["23"]
- else:
- self._soft_shadow_a = 0
- self._hard_shadow_a = 0
-
- def __draw_shadow__(self, origin, end, context=None):
- pass
+ Logger.warning(
+ "KivyMD: "
+ "The `FakeRectangularElevationBehavior` class has been "
+ "deprecated. Use the `CommonElevationBehavior` class instead."
+ )
class FakeCircularElevationBehavior(CommonElevationBehavior):
"""
- `FakeCircularElevationBehavior` is a shadow mockup for widgets. Improves
- performance using cached images inside `kivymd.images` dir
-
- This class cast a fake elliptic shadow behaind the widget.
-
- You can either use this behavior to overwrite the elevation of a prefab
- widget, or use it directly inside a new widget class definition.
-
- Use this class as follows for new widgets:
-
- .. code-block:: python
-
- class NewWidget(
- ThemableBehavior,
- FakeCircularElevationBehavior,
- SpecificBackgroundColorBehavior,
- # here you add the other front end classes for the widget front_end,
- ):
- [...]
-
- With this method each class can draw it's content in the canvas in the
- correct order, avoiding some visual errors.
-
- `FakeCircularElevationBehavior` will load prefabricated textures to optimize
- loading times.
-
- .. note:: About rounded corners:
- be careful, since this behavior is a mockup and will not draw any rounded
- corners. only perfect ellipses.
+ .. deprecated:: 1.1.0
+ Use :class:`~CommonElevationBehavior` class instead.
"""
def __init__(self, **kwargs):
- self._shadow = MDApp.get_running_app().theme_cls.round_shadow
- self.draw_shadow = WeakMethod(self.__draw_shadow__)
- self._fake_elevation = True
- self._update_shadow(self, self.elevation)
super().__init__(**kwargs)
-
- def _update_shadow(self, *args):
- if self._elevation > 0:
- # set shadow size
- width = self.width * 2
- height = self.height * 2
- center_x, center_y = self._get_center()
-
- x = center_x - width / 2
- self.soft_shadow_size = (width, height)
- self.hard_shadow_size = (width, height)
- # set ``soft_shadow`` parameters
- y = center_y - height / 2 - dp(0.5 * self._elevation)
- self.soft_shadow_pos = (x, y)
-
- # set ``hard_shadow`` parameters
- y = center_y - height / 2 - dp(0.5 * self._elevation)
- self.hard_shadow_pos = (x, y)
-
- # shadow transparency
- self._soft_shadow_a = 0.1 * 1.05**self._elevation
- self._hard_shadow_a = 0.4 * 0.8**self._elevation
- t = int(round(self._elevation))
- if 0 < t <= 23:
- if hasattr(self, "_shadow"):
- self._soft_shadow_texture = (
- self._hard_shadow_texture
- ) = self._shadow.textures[str(t)]
- else:
- self._soft_shadow_texture = (
- self._hard_shadow_texture
- ) = self._shadow.textures["23"]
- else:
- self._soft_shadow_a = 0
- self._hard_shadow_a = 0
-
- def __draw_shadow__(self, origin, end, context=None):
- pass
+ Logger.warning(
+ "KivyMD: "
+ "The `FakeCircularElevationBehavior` class has been deprecated. "
+ "Use the `CommonElevationBehavior` class instead."
+ )
diff --git a/sbapp/kivymd/uix/behaviors/ripple_behavior.py b/sbapp/kivymd/uix/behaviors/ripple_behavior.py
index f06e724b..12718781 100755
--- a/sbapp/kivymd/uix/behaviors/ripple_behavior.py
+++ b/sbapp/kivymd/uix/behaviors/ripple_behavior.py
@@ -111,7 +111,7 @@ from kivy.properties import (
from kivy.uix.behaviors import ToggleButtonBehavior
-class CommonRipple(object):
+class CommonRipple:
"""Base class for ripple effect."""
ripple_rad_default = NumericProperty(1)
diff --git a/sbapp/kivymd/uix/behaviors/rotate_behavior.py b/sbapp/kivymd/uix/behaviors/rotate_behavior.py
new file mode 100644
index 00000000..c80b8793
--- /dev/null
+++ b/sbapp/kivymd/uix/behaviors/rotate_behavior.py
@@ -0,0 +1,133 @@
+"""
+Behaviors/Rotate
+================
+
+.. versionadded:: 1.1.0
+
+Base class for controlling the rotate of the widget.
+
+.. note:: See `kivy.graphics.Rotate
+ <https://kivy.org/doc/stable/api-kivy.graphics.html#kivy.graphics.Rotate>`_
+ for more information.
+
+Kivy
+----
+
+.. code-block:: python
+
+ from kivy.animation import Animation
+ from kivy.lang import Builder
+ from kivy.app import App
+ from kivy.properties import NumericProperty
+ from kivy.uix.button import Button
+
+ KV = '''
+ Screen:
+
+ RotateButton:
+ size_hint: .5, .5
+ pos_hint: {"center_x": .5, "center_y": .5}
+ on_release: app.change_rotate(self)
+
+ canvas.before:
+ PushMatrix
+ Rotate:
+ angle: self.rotate_value_angle
+ axis: 0, 0, 1
+ origin: self.center
+ canvas.after:
+ PopMatrix
+ '''
+
+
+ class RotateButton(Button):
+ rotate_value_angle = NumericProperty(0)
+
+
+ class Test(App):
+ def build(self):
+ return Builder.load_string(KV)
+
+ def change_rotate(self, instance_button: Button) -> None:
+ Animation(rotate_value_angle=45, d=0.3).start(instance_button)
+
+
+ Test().run()
+
+KivyMD
+------
+
+.. code-block:: python
+
+ from kivy.animation import Animation
+ from kivy.lang import Builder
+ from kivy.uix.behaviors import ButtonBehavior
+
+ from kivymd.app import MDApp
+ from kivymd.uix.behaviors import RotateBehavior
+ from kivymd.uix.boxlayout import MDBoxLayout
+
+ KV = '''
+ MDScreen:
+
+ RotateBox:
+ size_hint: .5, .5
+ pos_hint: {"center_x": .5, "center_y": .5}
+ on_release: app.change_rotate(self)
+ md_bg_color: "red"
+ '''
+
+
+ class RotateBox(ButtonBehavior, RotateBehavior, MDBoxLayout):
+ pass
+
+
+ class Test(MDApp):
+ def build(self):
+ return Builder.load_string(KV)
+
+ def change_rotate(self, instance_button: RotateBox) -> None:
+ Animation(rotate_value_angle=45, d=0.3).start(instance_button)
+
+
+ Test().run()
+"""
+
+__all__ = ("RotateBehavior",)
+
+from kivy.lang import Builder
+from kivy.properties import ListProperty, NumericProperty
+
+Builder.load_string(
+ """
+<RotateBehavior>
+ canvas.before:
+ PushMatrix
+ Rotate:
+ angle: self.rotate_value_angle
+ axis: tuple(self.rotate_value_axis)
+ origin: self.center
+ canvas.after:
+ PopMatrix
+"""
+)
+
+
+class RotateBehavior:
+ """Base class for controlling the rotate of the widget."""
+
+ rotate_value_angle = NumericProperty(0)
+ """
+ Property for getting/setting the angle of the rotation.
+
+ :attr:`rotate_value_angle` is an :class:`~kivy.properties.NumericProperty`
+ and defaults to `0`.
+ """
+
+ rotate_value_axis = ListProperty((0, 0, 1))
+ """
+ Property for getting/setting the axis of the rotation.
+
+ :attr:`rotate_value_axis` is an :class:`~kivy.properties.ListProperty`
+ and defaults to `(0, 0, 1)`.
+ """
diff --git a/sbapp/kivymd/uix/behaviors/scale_behavior.py b/sbapp/kivymd/uix/behaviors/scale_behavior.py
new file mode 100644
index 00000000..bfc1e036
--- /dev/null
+++ b/sbapp/kivymd/uix/behaviors/scale_behavior.py
@@ -0,0 +1,156 @@
+"""
+Behaviors/Scale
+===============
+
+.. versionadded:: 1.1.0
+
+Base class for controlling the scale of the widget.
+
+.. note:: See `kivy.graphics.Rotate
+ <https://kivy.org/doc/stable/api-kivy.graphics.html#kivy.graphics.Scale>`_
+ for more information.
+
+Kivy
+----
+
+.. code-block:: python
+
+ from kivy.animation import Animation
+ from kivy.lang import Builder
+ from kivy.properties import NumericProperty
+ from kivy.uix.button import Button
+ from kivy.app import App
+
+
+ KV = '''
+ Screen:
+
+ ScaleButton:
+ size_hint: .5, .5
+ pos_hint: {"center_x": .5, "center_y": .5}
+ on_release: app.change_scale(self)
+
+ canvas.before:
+ PushMatrix
+ Scale:
+ x: self.scale_value_x
+ y: self.scale_value_y
+ z: self.scale_value_x
+ origin: self.center
+ canvas.after:
+ PopMatrix
+ '''
+
+
+ class ScaleButton(Button):
+ scale_value_x = NumericProperty(1)
+ scale_value_y = NumericProperty(1)
+ scale_value_z = NumericProperty(1)
+
+
+ class Test(App):
+ def build(self):
+ return Builder.load_string(KV)
+
+ def change_scale(self, instance_button: Button) -> None:
+ Animation(
+ scale_value_x=0.5,
+ scale_value_y=0.5,
+ scale_value_z=0.5,
+ d=0.3,
+ ).start(instance_button)
+
+
+ Test().run()
+
+KivyMD
+------
+
+.. code-block:: python
+
+ from kivy.animation import Animation
+ from kivy.lang import Builder
+ from kivy.uix.behaviors import ButtonBehavior
+
+ from kivymd.app import MDApp
+ from kivymd.uix.behaviors import ScaleBehavior
+ from kivymd.uix.boxlayout import MDBoxLayout
+
+ KV = '''
+ MDScreen:
+
+ ScaleBox:
+ size_hint: .5, .5
+ pos_hint: {"center_x": .5, "center_y": .5}
+ on_release: app.change_scale(self)
+ md_bg_color: "red"
+ '''
+
+
+ class ScaleBox(ButtonBehavior, ScaleBehavior, MDBoxLayout):
+ pass
+
+
+ class Test(MDApp):
+ def build(self):
+ return Builder.load_string(KV)
+
+ def change_scale(self, instance_button: ScaleBox) -> None:
+ Animation(
+ scale_value_x=0.5,
+ scale_value_y=0.5,
+ scale_value_z=0.5,
+ d=0.3,
+ ).start(instance_button)
+
+
+ Test().run()
+"""
+
+__all__ = ("ScaleBehavior",)
+
+from kivy.lang import Builder
+from kivy.properties import NumericProperty
+
+Builder.load_string(
+ """
+<ScaleBehavior>
+ canvas.before:
+ PushMatrix
+ Scale:
+ x: self.scale_value_x
+ y: self.scale_value_y
+ z: self.scale_value_x
+ origin: self.center
+ canvas.after:
+ PopMatrix
+"""
+)
+
+
+class ScaleBehavior:
+ """Base class for controlling the scale of the widget."""
+
+ scale_value_x = NumericProperty(1)
+ """
+ X-axis value.
+
+ :attr:`scale_value_x` is an :class:`~kivy.properties.NumericProperty`
+ and defaults to `1`.
+ """
+
+ scale_value_y = NumericProperty(1)
+ """
+ Y-axis value.
+
+ :attr:`scale_value_y` is an :class:`~kivy.properties.NumericProperty`
+ and defaults to `1`.
+ """
+
+ scale_value_z = NumericProperty(1)
+ """
+ Z-axis value.
+
+ :attr:`scale_value_z` is an :class:`~kivy.properties.NumericProperty`
+ and defaults to `1`.
+ """
diff --git a/sbapp/kivymd/uix/behaviors/stencil_behavior.py b/sbapp/kivymd/uix/behaviors/stencil_behavior.py
new file mode 100644
index 00000000..4bb5f6d2
--- /dev/null
+++ b/sbapp/kivymd/uix/behaviors/stencil_behavior.py
@@ -0,0 +1,134 @@
+"""
+Behaviors/Stencil
+=================
+
+.. versionadded:: 1.1.0
+
+Base class for controlling the stencil instructions of the widget.
+
+.. note:: See `Stencil instructions
+ <https://kivy.org/doc/stable/api-kivy.graphics.stencil_instructions.html>`_
+ for more information.
+
+Kivy
+----
+
+.. code-block:: python
+
+ from kivy.lang import Builder
+ from kivy.app import App
+
+ KV = '''
+ Carousel:
+
+ Button:
+ size_hint: .9, .8
+ pos_hint: {"center_x": .5, "center_y": .5}
+
+ canvas.before:
+ StencilPush
+ RoundedRectangle:
+ pos: root.pos
+ size: root.size
+ StencilUse
+ canvas.after:
+ StencilUnUse
+ RoundedRectangle:
+ pos: root.pos
+ size: root.size
+ StencilPop
+ '''
+
+
+ class Test(App):
+ def build(self):
+ return Builder.load_string(KV)
+
+
+ Test().run()
+
+KivyMD
+------
+
+.. code-block:: python
+
+ from kivy.lang import Builder
+
+ from kivymd.app import MDApp
+ from kivymd.uix.behaviors import StencilBehavior
+ from kivymd.uix.fitimage import FitImage
+
+ KV = '''
+ #:import os os
+ #:import images_path kivymd.images_path
+
+
+ MDCarousel:
+
+ StencilImage:
+ size_hint: .9, .8
+ pos_hint: {"center_x": .5, "center_y": .5}
+ source: os.path.join(images_path, "logo", "kivymd-icon-512.png")
+ '''
+
+
+ class StencilImage(FitImage, StencilBehavior):
+ pass
+
+
+ class Test(MDApp):
+ def build(self):
+ return Builder.load_string(KV)
+
+
+ Test().run()
+"""
+
+__all__ = ("StencilBehavior",)
+
+from kivy.lang import Builder
+from kivy.properties import VariableListProperty
+
+Builder.load_string(
+ """
+<StencilBehavior>
+ canvas.before:
+ StencilPush
+ RoundedRectangle:
+ pos: root.pos
+ size: root.size
+ # FIXME: Sometimes the radius has the value [], which get a
+ # `GraphicException: Invalid radius value, must be list of tuples/numerics` error
+ radius: root.radius if root.radius else [0, 0, 0, 0]
+ StencilUse
+ canvas.after:
+ StencilUnUse
+ RoundedRectangle:
+ pos: root.pos
+ size: root.size
+ # FIXME: Sometimes the radius has the value [], which get a
+ # `GraphicException: Invalid radius value, must be list of tuples/numerics` error
+ radius: root.radius if root.radius else [0, 0, 0, 0]
+ StencilPop
+"""
+)
+
+
+class StencilBehavior:
+ """Base class for controlling the stencil instructions of the widget."""
+
+ radius = VariableListProperty([0], length=4)
+ """
+ Canvas radius.
+
+ .. versionadded:: 1.0.0
+
+ .. code-block:: python
+
+ # Top left corner slice.
+ MDWidget:
+ radius: [25, 0, 0, 0]
+
+ :attr:`radius` is an :class:`~kivy.properties.VariableListProperty`
+ and defaults to `[0, 0, 0, 0]`.
+ """
diff --git a/sbapp/kivymd/uix/behaviors/toggle_behavior.py b/sbapp/kivymd/uix/behaviors/toggle_behavior.py
index 054849c0..c396a29c 100644
--- a/sbapp/kivymd/uix/behaviors/toggle_behavior.py
+++ b/sbapp/kivymd/uix/behaviors/toggle_behavior.py
@@ -14,59 +14,102 @@ example:
pass
-.. code-block:: python
+.. tabs::
- from kivy.lang import Builder
+ .. tab:: Declarative KV style
- from kivymd.app import MDApp
- from kivymd.uix.behaviors.toggle_behavior import MDToggleButton
- from kivymd.uix.button import MDRectangleFlatButton
+ .. code-block:: python
- KV = '''
- Screen:
+ from kivy.lang import Builder
- MDBoxLayout:
- adaptive_size: True
- pos_hint: {"center_x": .5, "center_y": .5}
+ from kivymd.app import MDApp
+ from kivymd.uix.behaviors.toggle_behavior import MDToggleButton
+ from kivymd.uix.button import MDFlatButton
- MyToggleButton:
- text: "Show ads"
- group: "x"
+ KV = '''
+ MDScreen:
- MyToggleButton:
- text: "Do not show ads"
- group: "x"
+ MDBoxLayout:
+ adaptive_size: True
+ spacing: "12dp"
+ pos_hint: {"center_x": .5, "center_y": .5}
- MyToggleButton:
- text: "Does not matter"
- group: "x"
- '''
+ MyToggleButton:
+ text: "Show ads"
+ group: "x"
+ MyToggleButton:
+ text: "Do not show ads"
+ group: "x"
- class MyToggleButton(MDRectangleFlatButton, MDToggleButton):
- def __init__(self, **kwargs):
- super().__init__(**kwargs)
- self.background_down = self.theme_cls.primary_light
+ MyToggleButton:
+ text: "Does not matter"
+ group: "x"
+ '''
- class Test(MDApp):
- def build(self):
- return Builder.load_string(KV)
+ class MyToggleButton(MDFlatButton, MDToggleButton):
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.background_down = self.theme_cls.primary_color
- Test().run()
+ class Test(MDApp):
+ def build(self):
+ self.theme_cls.theme_style = "Dark"
+ self.theme_cls.primary_palette = "Orange"
+ return Builder.load_string(KV)
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toggle-button-1.gif
- :align: center
-.. code-block:: python
+ Test().run()
+
+ .. tab:: Declarative python style
+
+ .. code-block:: python
+
+ from kivymd.app import MDApp
+ from kivymd.uix.behaviors.toggle_behavior import MDToggleButton
+ from kivymd.uix.boxlayout import MDBoxLayout
+ from kivymd.uix.button import MDFlatButton
+ from kivymd.uix.screen import MDScreen
- class MyToggleButton(MDFillRoundFlatButton, MDToggleButton):
- def __init__(self, **kwargs):
- self.background_down = MDApp.get_running_app().theme_cls.primary_dark
- super().__init__(**kwargs)
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toggle-button-2.gif
+ class MyToggleButton(MDFlatButton, MDToggleButton):
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.background_down = self.theme_cls.primary_color
+
+
+ class Test(MDApp):
+ def build(self):
+ self.theme_cls.theme_style = "Dark"
+ self.theme_cls.primary_palette = "Orange"
+ return (
+ MDScreen(
+ MDBoxLayout(
+ MyToggleButton(
+ text="Show ads",
+ group="x",
+ ),
+ MyToggleButton(
+ text="Do not show ads",
+ group="x",
+ ),
+ MyToggleButton(
+ text="Does not matter",
+ group="x",
+ ),
+ adaptive_size=True,
+ spacing="12dp",
+ pos_hint={"center_x": .5, "center_y": .5},
+ ),
+ )
+ )
+
+
+ Test().run()
+
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toggle-button-1.gif
:align: center
You can inherit the ``MyToggleButton`` class only from the following classes
@@ -88,6 +131,7 @@ from kivy.properties import BooleanProperty, ColorProperty
from kivy.uix.behaviors import ToggleButtonBehavior
from kivymd.uix.button import (
+ ButtonContentsIconText,
MDFillRoundFlatButton,
MDFillRoundFlatIconButton,
MDFlatButton,
@@ -149,7 +193,8 @@ class MDToggleButton(ToggleButtonBehavior):
# Do the object inherited from the "supported" buttons?
if not issubclass(self.__class__, classinfo):
raise ValueError(
- f"Class {self.__class__} must be inherited from one of the classes in the list {classinfo}"
+ f"Class {self.__class__} must be inherited from one of the "
+ f"classes in the list {classinfo}"
)
if (
not self.background_normal
@@ -165,10 +210,12 @@ class MDToggleButton(ToggleButtonBehavior):
):
self.__is_filled = True
self.background_normal = self.theme_cls.primary_color
- # If not the background_normal must be the same as the inherited one:
+ # If not background_normal must be the same as the inherited one.
else:
- self.background_normal = self.md_bg_color[:]
- # If no background_down is setted:
+ self.background_normal = (
+ self.md_bg_color[:] if self.md_bg_color else (0, 0, 0, 0)
+ )
+ # If no background_down is setter.
if (
not self.background_down
): # This means that if the value == [] or None will return True.
@@ -200,3 +247,6 @@ class MDToggleButton(ToggleButtonBehavior):
): # If the background is transparent, the font color must be the
# primary color.
self.text_color = self.font_color_normal
+
+ if issubclass(self.__class__, ButtonContentsIconText):
+ self.icon_color = self.text_color
diff --git a/sbapp/kivymd/uix/bottomnavigation/bottomnavigation.kv b/sbapp/kivymd/uix/bottomnavigation/bottomnavigation.kv
index 546b2c80..79f726b2 100644
--- a/sbapp/kivymd/uix/bottomnavigation/bottomnavigation.kv
+++ b/sbapp/kivymd/uix/bottomnavigation/bottomnavigation.kv
@@ -1,4 +1,3 @@
-#:import sm kivy.uix.screenmanager
#:import STANDARD_INCREMENT kivymd.material_resources.STANDARD_INCREMENT
@@ -7,9 +6,9 @@
height:
STANDARD_INCREMENT if app.theme_cls.material_style == "M2" else "80dp"
- MDScreenManager:
+ ScreenManager:
id: tab_manager
- transition: sm.FadeTransition(duration=.2)
+ transition: root.transition(duration=root.transition_duration)
on_current:
root.dispatch( \
"on_switch_tabs", \
@@ -96,7 +95,7 @@
radius: [16,]
size: root._selected_region_width, dp(32)
pos:
- self.center_x - self.width - dp(8), \
+ self.center_x - root._selected_region_width / 2, \
self.center_y - (dp(16))
MDLabel:
diff --git a/sbapp/kivymd/uix/bottomnavigation/bottomnavigation.py b/sbapp/kivymd/uix/bottomnavigation/bottomnavigation.py
index 9d3dbeb9..d99f54b0 100755
--- a/sbapp/kivymd/uix/bottomnavigation/bottomnavigation.py
+++ b/sbapp/kivymd/uix/bottomnavigation/bottomnavigation.py
@@ -62,59 +62,120 @@ For ease of understanding, this code works like this:
Example
-------
-.. code-block:: python
-
- from kivy.lang import Builder
-
- from kivymd.app import MDApp
-
-
- class Test(MDApp):
-
- def build(self):
- self.theme_cls.material_style = "M3"
- return Builder.load_string(
- '''
- MDScreen:
-
- MDBottomNavigation:
- panel_color: "#eeeaea"
- selected_color_background: "#97ecf8"
- text_color_active: 0, 0, 0, 1
-
- MDBottomNavigationItem:
- name: 'screen 1'
- text: 'Mail'
- icon: 'gmail'
- badge_icon: "numeric-10"
-
- MDLabel:
- text: 'Mail'
- halign: 'center'
-
- MDBottomNavigationItem:
- name: 'screen 2'
- text: 'Discord'
- icon: 'discord'
- badge_icon: "numeric-5"
-
- MDLabel:
- text: 'Discord'
- halign: 'center'
-
- MDBottomNavigationItem:
- name: 'screen 3'
- text: 'LinkedIN'
- icon: 'linkedin'
-
- MDLabel:
- text: 'LinkedIN'
- halign: 'center'
- '''
- )
-
-
- Test().run()
+.. tabs::
+
+ .. tab:: Declarative KV style
+
+ .. code-block:: python
+
+ from kivy.lang import Builder
+
+ from kivymd.app import MDApp
+
+
+ class Test(MDApp):
+
+ def build(self):
+ self.theme_cls.material_style = "M3"
+ self.theme_cls.theme_style = "Dark"
+ return Builder.load_string(
+ '''
+ MDScreen:
+
+ MDBottomNavigation:
+ #panel_color: "#eeeaea"
+ selected_color_background: "orange"
+ text_color_active: "lightgrey"
+
+ MDBottomNavigationItem:
+ name: 'screen 1'
+ text: 'Mail'
+ icon: 'gmail'
+ badge_icon: "numeric-10"
+
+ MDLabel:
+ text: 'Mail'
+ halign: 'center'
+
+ MDBottomNavigationItem:
+ name: 'screen 2'
+ text: 'Twitter'
+ icon: 'twitter'
+ badge_icon: "numeric-5"
+
+ MDLabel:
+ text: 'Twitter'
+ halign: 'center'
+
+ MDBottomNavigationItem:
+ name: 'screen 3'
+ text: 'LinkedIN'
+ icon: 'linkedin'
+
+ MDLabel:
+ text: 'LinkedIN'
+ halign: 'center'
+ '''
+ )
+
+
+ Test().run()
+
+ .. tab:: Declarative python style
+
+ .. code-block:: python
+
+ from kivymd.app import MDApp
+ from kivymd.uix.bottomnavigation import MDBottomNavigation, MDBottomNavigationItem
+ from kivymd.uix.label import MDLabel
+ from kivymd.uix.screen import MDScreen
+
+
+ class Test(MDApp):
+ def build(self):
+ self.theme_cls.material_style = "M3"
+ self.theme_cls.theme_style = "Dark"
+ return (
+ MDScreen(
+ MDBottomNavigation(
+ MDBottomNavigationItem(
+ MDLabel(
+ text='Mail',
+ halign='center',
+ ),
+ name='screen 1',
+ text='Mail',
+ icon='gmail',
+ badge_icon="numeric-10",
+ ),
+ MDBottomNavigationItem(
+ MDLabel(
+ text='Twitter',
+ halign='center',
+ ),
+ name='screen 1',
+ text='Twitter',
+ icon='twitter',
+ badge_icon="numeric-10",
+ ),
+ MDBottomNavigationItem(
+ MDLabel(
+ text='LinkedIN',
+ halign='center',
+ ),
+ name='screen 1',
+ text='LinkedIN',
+ icon='linkedin',
+ badge_icon="numeric-10",
+ ),
+ selected_color_background="orange",
+ text_color_active="lightgrey",
+ )
+ )
+ )
+
+
+ Test().run()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-navigation.gif
:align: center
@@ -192,16 +253,13 @@ from kivy.properties import (
)
from kivy.uix.behaviors import ButtonBehavior
from kivy.uix.boxlayout import BoxLayout
-from kivy.uix.screenmanager import ScreenManagerException
+from kivy.uix.screenmanager import FadeTransition, ScreenManagerException
from kivymd import uix_path
from kivymd.material_resources import STANDARD_INCREMENT
from kivymd.theming import ThemableBehavior, ThemeManager
from kivymd.uix.anchorlayout import MDAnchorLayout
-from kivymd.uix.behaviors import (
- DeclarativeBehavior,
- FakeRectangularElevationBehavior,
-)
+from kivymd.uix.behaviors import CommonElevationBehavior, DeclarativeBehavior
from kivymd.uix.behaviors.backgroundcolor_behavior import (
SpecificBackgroundColorBehavior,
)
@@ -413,6 +471,28 @@ class MDBottomNavigationItem(MDTab):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
+ def animate_header(
+ self, bottom_navigation_object, bottom_navigation_header_object
+ ) -> None:
+ if bottom_navigation_object.use_text:
+ Animation(_label_font_size=sp(12), d=0.1).start(
+ bottom_navigation_object.previous_tab.header
+ )
+ Animation(
+ _selected_region_width=0,
+ t="in_out_sine",
+ d=0,
+ ).start(bottom_navigation_header_object)
+ Animation(
+ _text_color_normal=bottom_navigation_header_object.text_color_normal
+ if bottom_navigation_object.previous_tab.header.text_color_normal
+ != [1, 1, 1, 1]
+ else self.theme_cls.disabled_hint_text_color,
+ d=0.1,
+ ).start(bottom_navigation_object.previous_tab.header)
+ bottom_navigation_object.previous_tab.header.active = False
+ self.header.active = True
+
def on_tab_press(self, *args) -> None:
"""Called when clicking on a panel item."""
@@ -420,28 +500,13 @@ class MDBottomNavigationItem(MDTab):
bottom_navigation_header_object = (
bottom_navigation_object.previous_tab.header
)
- bottom_navigation_object.ids.tab_manager.current = self.name
if bottom_navigation_object.previous_tab is not self:
- if bottom_navigation_object.use_text:
- Animation(_label_font_size=sp(12), d=0.1).start(
- bottom_navigation_object.previous_tab.header
- )
- Animation(
- _selected_region_width=0,
- t="in_out_sine",
- d=0,
- ).start(bottom_navigation_header_object)
- Animation(
- _text_color_normal=bottom_navigation_header_object.text_color_normal
- if bottom_navigation_object.previous_tab.header.text_color_normal
- != [1, 1, 1, 1]
- else self.theme_cls.disabled_hint_text_color,
- d=0.1,
- ).start(bottom_navigation_object.previous_tab.header)
- bottom_navigation_object.previous_tab.header.active = False
- self.header.active = True
- bottom_navigation_object.previous_tab = self
+ self.animate_header(
+ bottom_navigation_object, bottom_navigation_header_object
+ )
+
+ super().on_tab_press(*args)
def on_disabled(
self, instance_bottom_navigation_item, disabled_value: bool
@@ -498,6 +563,26 @@ class MDBottomNavigation(DeclarativeBehavior, TabbedPanelBase):
.. versionadded:: 1.0.0
"""
+ transition = ObjectProperty(FadeTransition)
+ """
+ Transition animation of bottom navigation screen manager.
+
+ .. versionadded:: 1.1.0
+
+ :attr:`transition` is an :class:`~kivy.properties.ObjectProperty`
+ and defaults to `FadeTransition`.
+ """
+
+ transition_duration = NumericProperty(0.2)
+ """
+ Duration animation of bottom navigation screen manager.
+
+ .. versionadded:: 1.1.0
+
+ :attr:`transition_duration` is an :class:`~kivy.properties.NumericProperty`
+ and defaults to `0.2`.
+ """
+
text_color_normal = ColorProperty([1, 1, 1, 1])
"""
Text color of the label when it is not selected.
@@ -772,8 +857,6 @@ class MDBottomNavigation(DeclarativeBehavior, TabbedPanelBase):
class MDBottomNavigationBar(
- ThemableBehavior,
- FakeRectangularElevationBehavior,
- MDFloatLayout,
+ ThemableBehavior, CommonElevationBehavior, MDFloatLayout
):
pass
diff --git a/sbapp/kivymd/uix/bottomsheet/bottomsheet.py b/sbapp/kivymd/uix/bottomsheet/bottomsheet.py
index bf7e98f3..522f31ba 100755
--- a/sbapp/kivymd/uix/bottomsheet/bottomsheet.py
+++ b/sbapp/kivymd/uix/bottomsheet/bottomsheet.py
@@ -34,7 +34,7 @@ Usage :class:`~MDListBottomSheet`
MDTopAppBar:
title: "Example BottomSheet"
pos_hint: {"top": 1}
- elevation: 10
+ elevation: 4
MDRaisedButton:
text: "Open list bottom sheet"
@@ -94,7 +94,7 @@ which will be used as an icon to the left of the item:
MDTopAppBar:
title: 'Example BottomSheet'
pos_hint: {"top": 1}
- elevation: 10
+ elevation: 4
MDRaisedButton:
text: "Open grid bottom sheet"
@@ -180,7 +180,7 @@ which will be used as an icon to the left of the item:
MDTopAppBar:
title: 'Example BottomSheet'
pos_hint: {"top": 1}
- elevation: 10
+ elevation: 4
MDRaisedButton:
text: "Open custom bottom sheet"
diff --git a/sbapp/kivymd/uix/boxlayout.py b/sbapp/kivymd/uix/boxlayout.py
index 11a640d5..6922de79 100644
--- a/sbapp/kivymd/uix/boxlayout.py
+++ b/sbapp/kivymd/uix/boxlayout.py
@@ -93,6 +93,8 @@ from kivymd.uix.behaviors import DeclarativeBehavior
class MDBoxLayout(DeclarativeBehavior, BoxLayout, MDAdaptiveWidget):
"""
- Box layout class. For more information, see in the
+ Box layout class.
+
+ For more information, see in the
:class:`~kivy.uix.boxlayout.BoxLayout` class documentation.
"""
diff --git a/sbapp/kivymd/uix/button/__init__.py b/sbapp/kivymd/uix/button/__init__.py
index dddf186d..b833a4cd 100644
--- a/sbapp/kivymd/uix/button/__init__.py
+++ b/sbapp/kivymd/uix/button/__init__.py
@@ -1,6 +1,7 @@
# NOQA F401
from .button import (
BaseButton,
+ ButtonContentsIconText,
MDFillRoundFlatButton,
MDFillRoundFlatIconButton,
MDFlatButton,
diff --git a/sbapp/kivymd/uix/button/button.kv b/sbapp/kivymd/uix/button/button.kv
index c7523a4e..4cee3390 100644
--- a/sbapp/kivymd/uix/button/button.kv
+++ b/sbapp/kivymd/uix/button/button.kv
@@ -3,9 +3,9 @@
Clear
Color:
rgba:
- (self._md_bg_color or [0.0, 0.0, 0.0, 0.0]) \
+ self._md_bg_color \
if not self.disabled else \
- (self._md_bg_color_disabled or [0.0, 0.0, 0.0, 0.0])
+ self._md_bg_color_disabled
RoundedRectangle:
size: self.size
pos: self.pos
@@ -13,19 +13,17 @@
radius: [root._radius, ]
Color:
rgba:
- root._line_color or [0.0, 0.0, 0.0, 0.0] \
+ root._line_color \
if not root.disabled else \
- ( \
- root._line_color_disabled \
- or self._disabled_color \
- or [0.0, 0.0, 0.0, 0.0] \
- )
+ (root._line_color_disabled or self._disabled_color)
Line:
width: root.line_width
rounded_rectangle:
- (self.x, self.y, self.width, self.height, \
+ ( \
+ self.x, self.y, self.width, self.height, \
root._radius, root._radius, root._radius, root._radius, \
- self.height)
+ self.height \
+ )
size_hint: None, None
anchor_x: root.halign
@@ -33,21 +31,28 @@
_round_rad: [self._radius] * 4
-
<ButtonContentsText>
lbl_txt: lbl_txt
width:
- max(root._min_width, \
- root.padding[0] + lbl_txt.texture_size[0] + root.padding[2])
+ max( \
+ root._min_width, \
+ root.padding[0] + lbl_txt.texture_size[0] + root.padding[2] \
+ )
size_hint_min_x:
- max(root._min_width, \
- root.padding[0] + lbl_txt.texture_size[0] + root.padding[2])
+ max( \
+ root._min_width, \
+ root.padding[0] + lbl_txt.texture_size[0] + root.padding[2] \
+ )
height:
- max(root._min_height, \
- root.padding[1] + lbl_txt.texture_size[1] + root.padding[3])
+ max( \
+ root._min_height, \
+ root.padding[1] + lbl_txt.texture_size[1] + root.padding[3] \
+ )
size_hint_min_y:
- max(root._min_height, \
- root.padding[1] + lbl_txt.texture_size[1] + root.padding[3])
+ max( \
+ root._min_height, \
+ root.padding[1] + lbl_txt.texture_size[1] + root.padding[3] \
+ )
MDLabel:
id: lbl_txt
@@ -84,7 +89,10 @@
# This is only a temporary fix and does not fix the cause of the error.
(root._icon_color if root._icon_color else root.theme_cls.text_color) \
if not root.disabled else \
- root.theme_cls.disabled_hint_text_color
+ root.theme_cls.disabled_hint_text_color \
+ if not root.disabled_color else \
+ root.disabled_color
+
on_icon:
if self.icon not in md_icons.keys(): self.size_hint = (1, 1)
theme_text_color: root._theme_icon_color
@@ -131,7 +139,7 @@
id: box
adaptive_size: True
padding: 0
- spacing: "4dp"
+ spacing: "8dp"
MDIcon:
id: lbl_ic
@@ -193,48 +201,21 @@
radius: [self.height / 2]
-<BaseFloatingRootButton>
+<MDFloatingRootButton>
theme_text_color: "Custom"
md_bg_color: self.theme_cls.primary_color
- canvas.before:
- PushMatrix
- Rotate:
- angle: self._angle
- axis: (0, 0, 1)
- origin: self.center
- canvas.after:
- PopMatrix
-
-
-# FIXME: Use :class:`~kivymd.uix.boxlayout.MDBoxLayout` instead
-# :class:`~kivy.uix.boxlayout.BoxLayout`.
-<BaseFloatingLabel>
- size_hint: None, None
- padding: "8dp", "4dp", "8dp", "4dp"
- height: label.texture_size[1] + self.padding[1] * 2
- width: label.texture_size[0] + self.padding[0] * 2
- elevation: 10
- # TODO: Use `md_bg_color` and `radius` instead `canvasю
- canvas:
+<MDFloatingLabel>
+ padding_x: "8dp"
+ padding_y: "8dp"
+ adaptive_size: True
+ theme_text_color: "Custom"
+
+ canvas.before:
Color:
- rgba:
- self.theme_cls.primary_color \
- if not root.bg_color else \
- root.bg_color
+ rgba: self.bg_color
RoundedRectangle:
- pos: self.pos
size: self.size
- radius: [5]
-
- Label:
- id: label
- markup: True
- text: root.text
- size_hint: None, None
- size: self.texture_size
- color:
- root.theme_cls.text_color \
- if not root.text_color else \
- root.text_color
+ pos: self.pos
+ radius: self.radius
diff --git a/sbapp/kivymd/uix/button/button.py b/sbapp/kivymd/uix/button/button.py
index 1ae6c310..0fac0e2e 100755
--- a/sbapp/kivymd/uix/button/button.py
+++ b/sbapp/kivymd/uix/button/button.py
@@ -33,30 +33,61 @@ Components/Button
MDIconButton
------------
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-icon-button.gif
- :align: center
+.. tabs::
-.. code-block:: python
+ .. tab:: Declarative KV style
- from kivy.lang import Builder
+ .. code-block:: python
- from kivymd.app import MDApp
+ from kivy.lang import Builder
- KV = '''
- MDScreen:
+ from kivymd.app import MDApp
- MDIconButton:
- icon: "language-python"
- pos_hint: {"center_x": .5, "center_y": .5}
- '''
+ KV = '''
+ MDScreen:
+ MDIconButton:
+ icon: "language-python"
+ pos_hint: {"center_x": .5, "center_y": .5}
+ '''
- class Example(MDApp):
- def build(self):
- return Builder.load_string(KV)
+ class Example(MDApp):
+ def build(self):
+ self.theme_cls.theme_style = "Dark"
+ self.theme_cls.primary_palette = "Orange"
+ return Builder.load_string(KV)
- Example().run()
+
+ Example().run()
+
+ .. tab:: Declarative python style
+
+ .. code-block:: python
+
+ from kivymd.app import MDApp
+ from kivymd.uix.button import MDIconButton
+ from kivymd.uix.screen import MDScreen
+
+
+ class Example(MDApp):
+ def build(self):
+ self.theme_cls.theme_style = "Dark"
+ self.theme_cls.primary_palette = "Orange"
+ return (
+ MDScreen(
+ MDIconButton(
+ icon="language-python",
+ pos_hint={"center_x": 0.5, "center_y": 0.5},
+ )
+ )
+ )
+
+
+ Example().run()
+
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-icon-button.png
+ :align: center
The :class:`~MDIconButton.icon` parameter must have the name of the icon
from ``kivymd/icon_definitions.py`` file.
@@ -66,9 +97,9 @@ You can also use custom icons:
.. code-block:: kv
MDIconButton:
- icon: "data/logo/kivy-icon-256.png"
+ icon: "kivymd/images/logo/kivymd-icon-256.png"
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-icon-custom-button.gif
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-icon-custom-button.png
:align: center
By default, :class:`~MDIconButton` button has a size ``(dp(48), dp (48))``.
@@ -80,7 +111,7 @@ Use :class:`~BaseButton.icon_size` attribute to resize the button:
icon: "android"
icon_size: "64sp"
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-icon-button-user-font-size.gif
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-icon-button-user-font-size.png
:align: center
By default, the color of :class:`~MDIconButton`
@@ -143,8 +174,10 @@ Material design style 3
'''
- class TestNavigationDrawer(MDApp):
+ class Example(MDApp):
def build(self):
+ self.theme_cls.theme_style = "Dark"
+ self.theme_cls.primary_palette = "Orange"
self.theme_cls.material_style = "M3"
return Builder.load_string(KV)
@@ -166,26 +199,23 @@ Material design style 3
)
- TestNavigationDrawer().run()
+ Example().run()
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-floating-action-button-m3.gif
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-floating-action-button-m3.png
:align: center
.. MDFlatButton:
MDFlatButton
------------
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-flat-button.gif
- :align: center
-
To change the text color of: class:`~MDFlatButton` use the ``text_color`` parameter:
.. code-block:: kv
MDFlatButton:
- text: "MDFLATBUTTON"
+ text: "MDFlatButton"
theme_text_color: "Custom"
- text_color: 0, 0, 1, 1
+ text_color: "orange"
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-flat-button-text-color.png
:align: center
@@ -195,7 +225,7 @@ Or use markup:
.. code-block:: kv
MDFlatButton:
- text: "[color=#00ffcc]MDFLATBUTTON[/color]"
+ text: "[color=#00ffcc]MDFlatButton[/color]"
To specify the font size and font name, use the parameters as in the usual
`Kivy` buttons:
@@ -203,7 +233,7 @@ To specify the font size and font name, use the parameters as in the usual
.. code-block:: kv
MDFlatButton:
- text: "MDFLATBUTTON"
+ text: "MDFlatButton"
font_size: "18sp"
font_name: "path/to/font"
@@ -211,33 +241,29 @@ To specify the font size and font name, use the parameters as in the usual
MDRaisedButton
--------------
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-raised-button.gif
- :align: center
-
This button is similar to the :class:`~MDFlatButton` button except that you
can set the background color for :class:`~MDRaisedButton`:
.. code-block:: kv
MDRaisedButton:
- text: "MDRAISEDBUTTON"
- md_bg_color: 1, 0, 1, 1
+ text: "MDRaisedButton"
+ md_bg_color: "red"
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-raised-button.png
+ :align: center
.. MDRectangleFlatButton:
MDRectangleFlatButton
---------------------
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-rectangle-flat-button.gif
- :align: center
-
.. code-block:: kv
MDRectangleFlatButton:
- text: "MDRECTANGLEFLATBUTTON"
+ text: "MDRectangleFlatButton"
theme_text_color: "Custom"
- text_color: 1, 0, 0, 1
- line_color: 0, 0, 1, 1
+ text_color: "white"
+ line_color: "red"
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-rectangle-flat-button-md-bg-color.png
:align: center
@@ -246,9 +272,6 @@ MDRectangleFlatButton
MDRectangleFlatIconButton
-------------------------
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-rectangle-flat-icon-button.png
- :align: center
-
Button parameters :class:`~MDRectangleFlatIconButton` are the same as
button :class:`~MDRectangleFlatButton`, with the addition of the
``theme_icon_color`` and ``icon_color`` parameters as for :class:`~MDIconButton`.
@@ -257,12 +280,12 @@ button :class:`~MDRectangleFlatButton`, with the addition of the
MDRectangleFlatIconButton:
icon: "android"
- text: "MDRECTANGLEFLATICONBUTTON"
+ text: "MDRectangleFlatIconButton"
theme_text_color: "Custom"
- text_color: 0, 0, 1, 1
- line_color: 1, 0, 1, 1
+ text_color: "white"
+ line_color: "red"
theme_icon_color: "Custom"
- icon_color: 1, 0, 0, 1
+ icon_color: "orange"
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-rectangle-flat-icon-button-custom.png
:align: center
@@ -279,16 +302,18 @@ Without border
class Example(MDApp):
def build(self):
- screen = MDScreen()
- screen.add_widget(
- MDRectangleFlatIconButton(
- text="MDRectangleFlatIconButton",
- icon="language-python",
- line_color=(0, 0, 0, 0),
- pos_hint={"center_x": .5, "center_y": .5},
+ self.theme_cls.theme_style = "Dark"
+ self.theme_cls.primary_palette = "Orange"
+ return (
+ MDScreen(
+ MDRectangleFlatIconButton(
+ text="MDRectangleFlatIconButton",
+ icon="language-python",
+ line_color=(0, 0, 0, 0),
+ pos_hint={"center_x": .5, "center_y": .5},
+ )
)
)
- return screen
Example().run()
@@ -301,6 +326,9 @@ Without border
line_color: 0, 0, 0, 0
pos_hint: {"center_x": .5, "center_y": .5}
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-rectangle-flat-icon-button-without-border.png
+ :align: center
+
.. MDRoundFlatButton:
MDRoundFlatButton
-----------------
@@ -308,8 +336,8 @@ MDRoundFlatButton
.. code-block:: kv
MDRoundFlatButton:
- text: "MDROUNDFLATBUTTON"
- text_color: 0, 1, 0, 1
+ text: "MDRoundFlatButton"
+ text_color: "white"
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-round-flat-button-text-color.png
:align: center
@@ -318,9 +346,6 @@ MDRoundFlatButton
MDRoundFlatIconButton
---------------------
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-round-flat-icon-button.png
- :align: center
-
Button parameters :class:`~MDRoundFlatIconButton` are the same as
button :class:`~MDRoundFlatButton`, with the addition of the
``theme_icon_color`` and ``icon_color`` parameters as for :class:`~MDIconButton`:
@@ -328,8 +353,12 @@ button :class:`~MDRoundFlatButton`, with the addition of the
.. code-block:: kv
MDRoundFlatIconButton:
+ text: "MDRoundFlatIconButton"
icon: "android"
- text: "MDROUNDFLATICONBUTTON"
+ text_color: "white"
+
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-round-flat-icon-button.png
+ :align: center
.. MDFillRoundFlatButton:
MDFillRoundFlatButton
@@ -359,14 +388,14 @@ button :class:`~MDRaisedButton`, with the addition of the
MDTextButton
------------
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-text-button.png
- :align: center
-
.. code-block:: kv
MDTextButton:
- text: "MDTEXTBUTTON"
- custom_color: 0, 1, 0, 1
+ text: "MDTextButton"
+ custom_color: "white"
+
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-text-button.png
+ :align: center
.. MDFloatingActionButtonSpeedDial:
MDFloatingActionButtonSpeedDial
@@ -398,6 +427,8 @@ MDFloatingActionButtonSpeedDial
}
def build(self):
+ self.theme_cls.theme_style = "Dark"
+ self.theme_cls.primary_palette = "Orange"
return Builder.load_string(KV)
@@ -408,30 +439,65 @@ MDFloatingActionButtonSpeedDial
Or without KV Language:
-.. code-block:: python
+.. tabs::
- from kivymd.uix.screen import MDScreen
- from kivymd.app import MDApp
- from kivymd.uix.button import MDFloatingActionButtonSpeedDial
+ .. tab:: Imperative python style
+ .. code-block:: python
- class Example(MDApp):
- data = {
- 'Python': 'language-python',
- 'PHP': 'language-php',
- 'C++': 'language-cpp',
- }
+ from kivymd.uix.screen import MDScreen
+ from kivymd.app import MDApp
+ from kivymd.uix.button import MDFloatingActionButtonSpeedDial
- def build(self):
- screen = MDScreen()
- speed_dial = MDFloatingActionButtonSpeedDial()
- speed_dial.data = self.data
- speed_dial.root_button_anim = True
- screen.add_widget(speed_dial)
- return screen
+ class Example(MDApp):
+ data = {
+ 'Python': 'language-python',
+ 'PHP': 'language-php',
+ 'C++': 'language-cpp',
+ }
- Example().run()
+ def build(self):
+ self.theme_cls.theme_style = "Dark"
+ self.theme_cls.primary_palette = "Orange"
+ screen = MDScreen()
+ speed_dial = MDFloatingActionButtonSpeedDial()
+ speed_dial.data = self.data
+ speed_dial.root_button_anim = True
+ screen.add_widget(speed_dial)
+ return screen
+
+
+ Example().run()
+
+ .. tab:: Declarative python style
+
+ .. code-block:: python
+
+ from kivymd.uix.screen import MDScreen
+ from kivymd.app import MDApp
+ from kivymd.uix.button import MDFloatingActionButtonSpeedDial
+
+
+ class Example(MDApp):
+ def build(self):
+ self.theme_cls.theme_style = "Dark"
+ self.theme_cls.primary_palette = "Orange"
+ return (
+ MDScreen(
+ MDFloatingActionButtonSpeedDial(
+ data={
+ 'Python': 'language-python',
+ 'PHP': 'language-php',
+ 'C++': 'language-cpp',
+ },
+ root_button_anim=True,
+ )
+ )
+ )
+
+
+ Example().run()
You can use various types of animation of labels for buttons on the stack:
@@ -443,21 +509,133 @@ You can use various types of animation of labels for buttons on the stack:
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-hint.gif
:align: center
-You can set your color values for background, text of buttons etc:
+You can set your color values for background, text of buttons etc:
.. code-block:: kv
MDFloatingActionButtonSpeedDial:
- bg_hint_color: app.theme_cls.primary_light
+ hint_animation: True
+ bg_hint_color: app.theme_cls.primary_dark
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-hint-color.png
:align: center
-.. seealso::
+Binds to individual buttons
+---------------------------
+
+.. tabs::
+
+ .. tab:: Declarative KV style
+
+ .. code-block:: python
+
+ from kivy.lang import Builder
+ from kivy.properties import DictProperty
+
+ from kivymd.app import MDApp
+
+ KV = '''
+ MDScreen:
+
+ MDFloatingActionButtonSpeedDial:
+ id: speed_dial
+ data: app.data
+ root_button_anim: True
+ hint_animation: True
+ '''
+
+
+ class Example(MDApp):
+ data = DictProperty()
+
+ def build(self):
+ self.theme_cls.theme_style = "Dark"
+ self.theme_cls.primary_palette = "Orange"
+ self.data = {
+ 'Python': 'language-python',
+ 'JS': [
+ 'language-javascript',
+ "on_press", lambda x: print("pressed JS"),
+ "on_release", lambda x: print(
+ "stack_buttons",
+ self.root.ids.speed_dial.stack_buttons
+ )
+ ],
+ 'PHP': [
+ 'language-php',
+ "on_press", lambda x: print("pressed PHP"),
+ "on_release", self.callback
+ ],
+ 'C++': [
+ 'language-cpp',
+ "on_press", lambda x: print("pressed C++"),
+ "on_release", lambda x: self.callback()
+ ],
+ }
+ return Builder.load_string(KV)
+
+ def callback(self, *args):
+ print(args)
+
+
+ Example().run()
+
+ .. tab:: Declarative python style
+
+ .. code-block:: python
+
+ from kivymd.app import MDApp
+ from kivymd.uix.button import MDFloatingActionButtonSpeedDial
+ from kivymd.uix.screen import MDScreen
+
+
+ class Example(MDApp):
+ def build(self):
+ self.theme_cls.theme_style = "Dark"
+ self.theme_cls.primary_palette = "Orange"
+ return (
+ MDScreen(
+ MDFloatingActionButtonSpeedDial(
+ id="speed_dial",
+ hint_animation=True,
+ root_button_anim=True,
+ )
+ )
+ )
- `See full example <https://github.com/kivymd/KivyMD/wiki/Components-Button>`_
+ def on_start(self):
+ data = {
+ "Python": "language-python",
+ "JS": [
+ "language-javascript",
+ "on_press", lambda x: print("pressed JS"),
+ "on_release", lambda x: print(
+ "stack_buttons",
+ self.root.ids.speed_dial.stack_buttons
+ )
+ ],
+ "PHP": [
+ "language-php",
+ "on_press", lambda x: print("pressed PHP"),
+ "on_release", self.callback
+ ],
+ "C++": [
+ "language-cpp",
+ "on_press", lambda x: print("pressed C++"),
+ "on_release", lambda x: self.callback()
+ ],
+ }
+ self.root.ids.speed_dial.data = data
+
+ def callback(self, *args):
+ print(args)
+
+
+ Example().run()
"""
+from __future__ import annotations
+
__all__ = (
"BaseButton",
"MDIconButton",
@@ -495,8 +673,8 @@ from kivy.properties import (
)
from kivy.uix.anchorlayout import AnchorLayout
from kivy.uix.behaviors import ButtonBehavior
-from kivy.uix.boxlayout import BoxLayout
from kivy.uix.floatlayout import FloatLayout
+from kivy.weakproxy import WeakProxy
from kivymd import uix_path
from kivymd.color_definitions import text_colors
@@ -505,9 +683,8 @@ from kivymd.theming import ThemableBehavior
from kivymd.uix.behaviors import (
CommonElevationBehavior,
DeclarativeBehavior,
- FakeRectangularElevationBehavior,
RectangularRippleBehavior,
- RoundedRectangularElevationBehavior,
+ RotateBehavior,
)
from kivymd.uix.label import MDLabel
from kivymd.uix.tooltip import MDTooltip
@@ -527,6 +704,62 @@ theme_text_color_options = (
"ContrastParentBackground",
)
+# FIXME: If you set a new elevation value for the button
+# (press the "Set elevation" button), then disable the button
+# (press the "Disabled" button), and then enable the button
+# (press the "Undisabled" button), then the previously set elevation value is
+# reset to zero.
+# In addition, if you set a new elevation value
+# (press the "Set elevation" button) and click on the button for which we set
+# the elevation value, then the new elevation value will receive the previous
+# elevation value. This problem is only related to the buttons.
+# For example, there is no such problem for the MDCard widget.
+
+"""
+from kivy.lang import Builder
+
+from kivymd.app import MDApp
+
+KV = '''
+MDScreen:
+
+ MDRaisedButton:
+ size_hint: .5, .5
+ id: button
+ pos_hint: {"center_x": .5, "center_y": .5}
+ elevation: 0
+
+ MDBoxLayout:
+ adaptive_size: True
+ pos_hint: {"center_x": .5}
+ spacing: 12
+ padding: 12
+
+ MDRaisedButton:
+ text: "Set elevation"
+ pos_hint: {"center_x": .5, "bottom": 1}
+ on_release: button.elevation = 4
+
+ MDRaisedButton:
+ text: "Disabled"
+ pos_hint: {"center_x": .5, "bottom": 1}
+ on_release: button.disabled = True
+
+ MDRaisedButton:
+ text: "Undisabled"
+ pos_hint: {"center_x": .5, "bottom": 1}
+ on_release: button.disabled = False
+'''
+
+
+class Test(MDApp):
+ def build(self):
+ return Builder.load_string(KV)
+
+
+Test().run()
+"""
+
class BaseButton(
DeclarativeBehavior,
@@ -535,7 +768,12 @@ class BaseButton(
ButtonBehavior,
AnchorLayout,
):
- """Base class for all buttons."""
+ """
+ Base class for all buttons.
+
+ For more information, see in the
+ :class:`~kivy.uix.anchorlayout.AnchorLayout` class documentation.
+ """
padding = VariableListProperty([dp(16), dp(8), dp(16), dp(8)])
"""
@@ -621,7 +859,7 @@ class BaseButton(
text_color = ColorProperty(None)
"""
- Button text color in (r, g, b, a) format.
+ Button text color in (r, g, b, a) or string format.
:attr:`text_color` is a :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
@@ -629,7 +867,7 @@ class BaseButton(
icon_color = ColorProperty(None)
"""
- Button icon color in (r, g, b, a) format.
+ Button icon color in (r, g, b, a) or string format.
:attr:`icon_color` is a :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
@@ -672,7 +910,7 @@ class BaseButton(
line_color = ColorProperty(None)
"""
- Line color for button border.
+ Line color in (r, g, b, a) or string format for button border.
:attr:`line_color` is a :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
@@ -680,7 +918,7 @@ class BaseButton(
line_color_disabled = ColorProperty(None)
"""
- Disabled line color for button border.
+ Disabled line color in (r, g, b, a) or string format for button border.
.. versionadded:: 1.0.0
@@ -690,7 +928,7 @@ class BaseButton(
md_bg_color = ColorProperty(None)
"""
- Button background color.
+ Button background color in (r, g, b, a) or string format.
:attr:`md_bg_color` is a :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
@@ -698,7 +936,8 @@ class BaseButton(
md_bg_color_disabled = ColorProperty(None)
"""
- The background color of the button when the button is disabled.
+ The background color in (r, g, b, a) or string format of the button when
+ the button is disabled.
:attr:`md_bg_color_disabled` is a :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
@@ -706,8 +945,8 @@ class BaseButton(
disabled_color = ColorProperty(None)
"""
- The color of the text and icon when the button is disabled, in the
- (r, g, b, a) format.
+ The color of the text and icon when the button is disabled,
+ in (r, g, b, a) or string format.
.. versionadded:: 1.0.0
@@ -728,11 +967,11 @@ class BaseButton(
# Note - _radius must be > 0 to avoid rendering issues.
_radius = BoundedNumericProperty(dp(4), min=0.0999, errorvalue=0.1)
# Properties used for rendering.
- _disabled_color = ColorProperty(None)
- _md_bg_color = ColorProperty(None)
- _md_bg_color_disabled = ColorProperty(None)
- _line_color = ColorProperty(None)
- _line_color_disabled = ColorProperty(None)
+ _disabled_color = ColorProperty([0.0, 0.0, 0.0, 0.0])
+ _md_bg_color = ColorProperty([0.0, 0.0, 0.0, 0.0])
+ _md_bg_color_disabled = ColorProperty([0.0, 0.0, 0.0, 0.0])
+ _line_color = ColorProperty([0.0, 0.0, 0.0, 0.0])
+ _line_color_disabled = ColorProperty([0.0, 0.0, 0.0, 0.0])
_theme_text_color = OptionProperty(None, options=theme_text_color_options)
_theme_icon_color = OptionProperty(None, options=theme_text_color_options)
_text_color = ColorProperty(None)
@@ -754,8 +993,8 @@ class BaseButton(
_animation_fade_bg = ObjectProperty(None, allownone=True)
- def __init__(self, **kwargs):
- super().__init__(**kwargs)
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
self.theme_cls.bind(
primary_palette=self.set_all_colors,
theme_style=self.set_all_colors,
@@ -809,14 +1048,14 @@ class BaseButton(
"""Set all button colours (except text/icons)."""
# Set main color
- self._md_bg_color = (
+ _md_bg_color = (
self.md_bg_color
or self._default_md_bg_color
or self.theme_cls.primary_color
)
# Set disabled color
- self._md_bg_color_disabled = (
+ _md_bg_color_disabled = (
self.md_bg_color_disabled
or (
[sum(self.md_bg_color[0:3]) / 3.0] * 3
@@ -829,14 +1068,14 @@ class BaseButton(
)
# Set line color
- self._line_color = (
+ _line_color = (
self.line_color
or self._default_line_color
or self.theme_cls.primary_color
)
# Set disabled line color
- self._line_color_disabled = (
+ _line_color_disabled = (
self.line_color_disabled
or (
[sum(self.line_color[0:3]) / 3.0] * 3
@@ -848,6 +1087,21 @@ class BaseButton(
or self.theme_cls.disabled_primary_color
)
+ if self.theme_cls.theme_style_switch_animation:
+ Animation(
+ _md_bg_color=_md_bg_color,
+ _md_bg_color_disabled=_md_bg_color_disabled,
+ _line_color=_line_color,
+ _line_color_disabled=_line_color_disabled,
+ d=self.theme_cls.theme_style_switch_animation_duration,
+ t="linear",
+ ).start(self)
+ else:
+ self._md_bg_color = _md_bg_color
+ self._md_bg_color_disabled = _md_bg_color_disabled
+ self._line_color = _line_color
+ self._line_color_disabled = _line_color_disabled
+
def set_text_color(self, *args) -> None:
"""
Set _theme_text_color and _text_color based on defaults and options.
@@ -872,7 +1126,9 @@ class BaseButton(
"""
self._theme_icon_color = (
- self.theme_icon_color or self._default_theme_icon_color
+ (self.theme_icon_color or self._default_theme_icon_color)
+ if not self.disabled
+ else "Custom"
)
if self._default_icon_color == "PrimaryHue":
default_icon_color = text_colors[self.theme_cls.primary_palette][
@@ -931,6 +1187,10 @@ class BaseButton(
return super().on_touch_up(touch)
def on_disabled(self, instance_button, disabled_value: bool) -> None:
+ if hasattr(super(), "on_disabled"):
+ if self.disabled is True:
+ Animation.cancel_all(self, "elevation")
+ super().on_disabled(instance_button, disabled_value)
Clock.schedule_once(self.set_disabled_color)
@@ -948,30 +1208,20 @@ class ButtonElevationBehaviour(CommonElevationBehavior):
_elevation_raised = NumericProperty()
_anim_raised = ObjectProperty(None, allownone=True)
- _default_elevation = 2
+ _default_elevation = 3
def __init__(self, **kwargs):
+ super().__init__(**kwargs)
if self.elevation == 0:
self.elevation = self._default_elevation
- super().__init__(**kwargs)
- self.bind(_radius=self.setter("radius"))
- self.on_elevation(self, self.elevation)
-
- def on_elevation(self, instance_button, elevation_value: int) -> None:
- super().on_elevation(instance_button, elevation_value)
- self._elevation_raised = self.elevation + 6
+ if hasattr(self, "radius"):
+ self.bind(_radius=self.setter("radius"))
+ Clock.schedule_once(self.create_anim_raised)
self.on_disabled(self, self.disabled)
- def on__elevation_raised(
- self, instance_button, elevation_value: int
- ) -> None:
- Animation.cancel_all(self, "_elevation")
- self._anim_raised = Animation(_elevation=self._elevation_raised, d=0.15)
-
- def on_disabled(self, instance_button, disabled_value: bool) -> None:
- if self.disabled is True:
- Animation.cancel_all(self, "_elevation")
- super().on_disabled(instance_button, disabled_value)
+ def create_anim_raised(self, *args) -> None:
+ self._elevation_raised = self.elevation + 1.2
+ self._anim_raised = Animation(elevation=self.elevation + 1, d=0.15)
def on_touch_down(self, touch):
if not self.disabled:
@@ -994,8 +1244,8 @@ class ButtonElevationBehaviour(CommonElevationBehavior):
return super().on_touch_up(touch)
def stop_elevation_anim(self):
- Animation.cancel_all(self, "_elevation")
- self._elevation = self.elevation
+ Animation.cancel_all(self, "elevation")
+ self.elevation = self._elevation_raised - 1
class ButtonContentsText:
@@ -1059,7 +1309,7 @@ class OldButtonIconMixin:
self.theme_icon_color = "Custom"
-class MDFlatButton(ButtonContentsText, BaseButton):
+class MDFlatButton(BaseButton, ButtonContentsText):
"""
A flat rectangular button with (by default) no border or background.
Text is the default text color.
@@ -1080,12 +1330,7 @@ class MDFlatButton(ButtonContentsText, BaseButton):
"""
-class MDRaisedButton(
- FakeRectangularElevationBehavior,
- ButtonElevationBehaviour,
- ButtonContentsText,
- BaseButton,
-):
+class MDRaisedButton(BaseButton, ButtonElevationBehaviour, ButtonContentsText):
"""
A flat button with (by default) a primary color fill and matching
color text.
@@ -1098,8 +1343,14 @@ class MDRaisedButton(
_default_theme_text_color = "Custom"
_default_text_color = "PrimaryHue"
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.shadow_softness = 8
+ self.shadow_offset = (0, 2)
+ self.shadow_radius = self._radius * 2
-class MDRectangleFlatButton(ButtonContentsText, BaseButton):
+
+class MDRectangleFlatButton(BaseButton, ButtonContentsText):
"""
A flat button with (by default) a primary color border and primary
color text.
@@ -1112,7 +1363,7 @@ class MDRectangleFlatButton(ButtonContentsText, BaseButton):
class MDRectangleFlatIconButton(
- OldButtonIconMixin, ButtonContentsIconText, BaseButton
+ BaseButton, OldButtonIconMixin, ButtonContentsIconText
):
"""
A flat button with (by default) a primary color border, primary color text
@@ -1127,7 +1378,7 @@ class MDRectangleFlatIconButton(
_default_icon_color = "Primary"
-class MDRoundFlatButton(ButtonContentsText, BaseButton):
+class MDRoundFlatButton(BaseButton, ButtonContentsText):
"""
A flat button with (by default) fully rounded corners, a primary
color border and primary color text.
@@ -1138,15 +1389,13 @@ class MDRoundFlatButton(ButtonContentsText, BaseButton):
_default_theme_text_color = "Custom"
_default_text_color = "Primary"
- def __init__(self, **kwargs):
- super().__init__(**kwargs)
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
self.rounded_button = True
class MDRoundFlatIconButton(
- OldButtonIconMixin,
- ButtonContentsIconText,
- BaseButton,
+ BaseButton, OldButtonIconMixin, ButtonContentsIconText
):
"""
A flat button with (by default) rounded corners, a primary color border,
@@ -1160,12 +1409,12 @@ class MDRoundFlatIconButton(
_default_text_color = "Primary"
_default_icon_color = "Primary"
- def __init__(self, **kwargs):
- super().__init__(**kwargs)
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
self.rounded_button = True
-class MDFillRoundFlatButton(ButtonContentsText, BaseButton):
+class MDFillRoundFlatButton(BaseButton, ButtonContentsText):
"""
A flat button with (by default) rounded corners, a primary color fill
and primary color text.
@@ -1176,15 +1425,13 @@ class MDFillRoundFlatButton(ButtonContentsText, BaseButton):
_default_theme_text_color = "Custom"
_default_text_color = "PrimaryHue"
- def __init__(self, **kwargs):
- super().__init__(**kwargs)
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
self.rounded_button = True
class MDFillRoundFlatIconButton(
- OldButtonIconMixin,
- ButtonContentsIconText,
- BaseButton,
+ BaseButton, OldButtonIconMixin, ButtonContentsIconText
):
"""
A flat button with (by default) rounded corners, a primary color fill,
@@ -1198,12 +1445,12 @@ class MDFillRoundFlatIconButton(
_default_text_color = "PrimaryHue"
_default_icon_color = "PrimaryHue"
- def __init__(self, **kwargs):
- super().__init__(**kwargs)
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
self.rounded_button = True
-class MDIconButton(OldButtonIconMixin, ButtonContentsIcon, BaseButton):
+class MDIconButton(BaseButton, OldButtonIconMixin, ButtonContentsIcon):
"""A simple rounded icon button."""
icon = StringProperty("checkbox-blank-circle")
@@ -1217,8 +1464,8 @@ class MDIconButton(OldButtonIconMixin, ButtonContentsIcon, BaseButton):
_min_width = NumericProperty(0)
_default_icon_pad = max(dp(48) - sp(24), 0)
- def __init__(self, **kwargs):
- super().__init__(**kwargs)
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
self.rounded_button = True
# FIXME: GraphicException: Invalid width value, must be > 0
self.line_width = 0.001
@@ -1236,11 +1483,7 @@ class MDIconButton(OldButtonIconMixin, ButtonContentsIcon, BaseButton):
class MDFloatingActionButton(
- OldButtonIconMixin,
- RoundedRectangularElevationBehavior,
- ButtonElevationBehaviour,
- ButtonContentsIcon,
- BaseButton,
+ BaseButton, OldButtonIconMixin, ButtonElevationBehaviour, ButtonContentsIcon
):
"""
Implementation
@@ -1268,12 +1511,11 @@ class MDFloatingActionButton(
_default_theme_icon_color = "Custom"
_default_icon_color = "PrimaryHue"
- def __init__(self, **kwargs):
- super().__init__(**kwargs)
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
# FIXME: GraphicException: Invalid width value, must be > 0
self.line_width = 0.001
- self.theme_cls.bind(material_style=self.set_size)
- self.theme_cls.bind(material_style=self.set__radius)
+ self.theme_cls.bind(material_style=self.set_size_and_radius)
Clock.schedule_once(self.set_size)
Clock.schedule_once(self.set__radius)
Clock.schedule_once(self.set_font_size)
@@ -1287,9 +1529,13 @@ class MDFloatingActionButton(
def set__radius(self, *args) -> None:
if self.theme_cls.material_style == "M2":
+ self.shadow_radius = self.height / 2
self.rounded_button = True
else:
+ self.shadow_softness = 8
+ self.shadow_offset = (0, 2)
self.rounded_button = False
+
if self.type == "small":
self._radius = dp(12)
elif self.type == "standard":
@@ -1297,6 +1543,12 @@ class MDFloatingActionButton(
elif self.type == "large":
self._radius = dp(28)
+ self.shadow_radius = self._radius
+
+ def set_size_and_radius(self, *args) -> None:
+ self.set_size(args)
+ self.set__radius(args)
+
def set_size(self, *args) -> None:
if self.theme_cls.material_style == "M2":
self.size = dp(56), dp(56)
@@ -1316,7 +1568,7 @@ class MDFloatingActionButton(
class MDTextButton(ButtonBehavior, MDLabel):
color = ColorProperty(None)
"""
- Button color in (r, g, b, a) format.
+ Button color in (r, g, b, a) or string format.
:attr:`color` is a :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
@@ -1324,7 +1576,7 @@ class MDTextButton(ButtonBehavior, MDLabel):
color_disabled = ColorProperty(None)
"""
- Button color disabled in (r, g, b, a) format.
+ Button color disabled in (r, g, b, a) or string format.
:attr:`color_disabled` is a :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
@@ -1357,14 +1609,6 @@ class MDTextButton(ButtonBehavior, MDLabel):
# SpeedDial classes
-class BaseFloatingRootButton(MDFloatingActionButton):
- _angle = NumericProperty(0)
-
- def __init__(self, **kwargs):
- super().__init__(**kwargs)
- self.elevation = 5
-
-
class BaseFloatingBottomButton(MDFloatingActionButton, MDTooltip):
_canvas_width = NumericProperty(0)
_padding_right = NumericProperty(0)
@@ -1375,41 +1619,48 @@ class BaseFloatingBottomButton(MDFloatingActionButton, MDTooltip):
self.height = "46dp"
-# FIXME: Use :class:`~kivymd.uix.boxlayout.MDBoxLayout` instead
-# :class:`~kivy.uix.boxlayout.BoxLayout`.
-class BaseFloatingLabel(
- ThemableBehavior, FakeRectangularElevationBehavior, BoxLayout
-):
- text = StringProperty()
- text_color = ColorProperty(None)
- bg_color = ColorProperty(None)
-
-
class MDFloatingBottomButton(BaseFloatingBottomButton):
- pass
+ _bg_color = ColorProperty(None)
-class MDFloatingRootButton(BaseFloatingRootButton):
- pass
+class MDFloatingRootButton(RotateBehavior, MDFloatingActionButton):
+ rotate_value_angle = NumericProperty(0)
-class MDFloatingLabel(BaseFloatingLabel):
- pass
+class MDFloatingLabel(MDLabel):
+ bg_color = ColorProperty([0, 0, 0, 0])
-class MDFloatingActionButtonSpeedDial(ThemableBehavior, FloatLayout):
+class MDFloatingActionButtonSpeedDial(
+ DeclarativeBehavior, ThemableBehavior, FloatLayout
+):
"""
+ For more information, see in the
+ :class:`~kivy.uix.floatlayout.FloatLayout` class documentation.
+
:Events:
:attr:`on_open`
Called when a stack is opened.
:attr:`on_close`
Called when a stack is closed.
+ :attr:`on_press_stack_button`
+ Called at the on_press event for the stack button.
+ :attr:`on_release_stack_button`
+ Called at the on_press event for the stack button.
"""
icon = StringProperty("plus")
"""
Root button icon name.
+ .. code-block:: kv
+
+ MDFloatingActionButtonSpeedDial:
+ icon: "pencil"
+
+ .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-icon.png
+ :align: center
+
:attr:`icon` is a :class:`~kivy.properties.StringProperty`
and defaults to `'plus'`.
"""
@@ -1422,36 +1673,59 @@ class MDFloatingActionButtonSpeedDial(ThemableBehavior, FloatLayout):
and defaults to `'right'`.
"""
- callback = ObjectProperty(lambda x: None)
+ label_text_color = ColorProperty(None)
"""
- Custom callback.
+ Color of floating text labels in (r, g, b, a) or string format.
.. code-block:: kv
MDFloatingActionButtonSpeedDial:
- callback: app.callback
+ label_text_color: "orange"
- .. code-block:: python
+ .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-label-text-color.png
+ :align: center
- def callback(self, instance):
- print(instance.icon)
+ :attr:`label_text_color` is a :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
+ label_bg_color = ColorProperty([0, 0, 0, 0])
+ """
+ Background color of floating text labels in (r, g, b, a) or string format.
- :attr:`callback` is a :class:`~kivy.properties.ObjectProperty`
- and defaults to `None`.
+ .. code-block:: kv
+
+ MDFloatingActionButtonSpeedDial:
+ label_text_color: "black"
+ label_bg_color: "orange"
+
+ .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-label-bg-color.png
+ :align: center
+
+ :attr:`label_bg_color` is a :class:`~kivy.properties.ColorProperty`
+ and defaults to `[0, 0, 0, 0]`.
"""
- label_text_color = ColorProperty([0, 0, 0, 1])
+ label_radius = VariableListProperty([0], length=4)
"""
- Floating text color in (r, g, b, a) format.
+ The radius of the background of floating text labels.
- :attr:`label_text_color` is a :class:`~kivy.properties.ColorProperty`
- and defaults to `[0, 0, 0, 1]`.
+ .. code-block:: kv
+
+ MDFloatingActionButtonSpeedDial:
+ label_text_color: "black"
+ label_bg_color: "orange"
+
+ .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-label-radius.png
+ :align: center
+
+ :attr:`label_radius` is a :class:`~kivy.properties.ColorProperty`
+ and defaults to `[0, 0, 0, 0]`.
"""
data = DictProperty()
"""
- Must be a dictionary
+ Must be a dictionary.
.. code-block:: python
@@ -1462,18 +1736,33 @@ class MDFloatingActionButtonSpeedDial(ThemableBehavior, FloatLayout):
}
"""
- right_pad = BooleanProperty(True)
+ right_pad = BooleanProperty(False)
"""
- If `True`, the button will increase on the right side by 2.5 pixels
- if the :attr:`~hint_animation` parameter equal to `True`.
+ If `True`, the background for the floating text label will increase by the
+ number of pixels specified in the :attr:`~right_pad_value` parameter.
+
+ Works only if the :attr:`~hint_animation` parameter is set to `True`.
.. rubric:: False
+ .. code-block:: kv
+
+ MDFloatingActionButtonSpeedDial:
+ hint_animation: True
+ right_pad: False
+
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-right-pad.gif
:align: center
.. rubric:: True
+ .. code-block:: kv
+
+ MDFloatingActionButtonSpeedDial:
+ hint_animation: True
+ right_pad: True
+ right_pad_value: "10dp"
+
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-right-pad-true.gif
:align: center
@@ -1481,6 +1770,14 @@ class MDFloatingActionButtonSpeedDial(ThemableBehavior, FloatLayout):
and defaults to `False`.
"""
+ right_pad_value = NumericProperty(0)
+ """
+ See :attr:`~right_pad` parameter for more information.
+
+ :attr:`right_pad_value` is a :class:`~kivy.properties.NumericProperty`
+ and defaults to `0`.
+ """
+
root_button_anim = BooleanProperty(False)
"""
If ``True`` then the root button will rotate 45 degrees when the stack
@@ -1569,39 +1866,87 @@ class MDFloatingActionButtonSpeedDial(ThemableBehavior, FloatLayout):
bg_color_root_button = ColorProperty(None)
"""
- Root button color in (r, g, b, a) format.
+ Background color of root button in (r, g, b, a) or string format.
+
+ .. code-clock:: kv
+
+ MDFloatingActionButtonSpeedDial:
+ bg_color_root_button: "red"
+
+ .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-bg-color-root-button.png
+ :align: center
:attr:`bg_color_root_button` is a :class:`~kivy.properties.ColorProperty`
- and defaults to `[]`.
+ and defaults to `None`.
"""
bg_color_stack_button = ColorProperty(None)
"""
- The color of the buttons in the stack (r, g, b, a) format.
+ Background color of the stack buttons in (r, g, b, a) or string format.
+
+ .. code-clock:: kv
+
+ MDFloatingActionButtonSpeedDial:
+ bg_color_root_button: "red"
+ bg_color_stack_button: "red"
+
+ .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-bg-color-stack-button.png
+ :align: center
:attr:`bg_color_stack_button` is a :class:`~kivy.properties.ColorProperty`
- and defaults to `[]`.
+ and defaults to `None`.
"""
color_icon_stack_button = ColorProperty(None)
"""
- The color icon of the buttons in the stack (r, g, b, a) format.
+ The color icon of the stack buttons in (r, g, b, a) or string format.
+
+ .. code-clock:: kv
+
+ MDFloatingActionButtonSpeedDial:
+ bg_color_root_button: "red"
+ bg_color_stack_button: "red"
+ color_icon_stack_button: "white"
+
+ .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-color-icon-stack-button.png
+ :align: center
:attr:`color_icon_stack_button` is a :class:`~kivy.properties.ColorProperty`
- and defaults to `[]`.
+ and defaults to `None`.
"""
color_icon_root_button = ColorProperty(None)
"""
- The color icon of the root button (r, g, b, a) format.
+ The color icon of the root button in (r, g, b, a) or string format.
+
+ .. code-clock:: kv
+
+ MDFloatingActionButtonSpeedDial:
+ bg_color_root_button: "red"
+ bg_color_stack_button: "red"
+ color_icon_stack_button: "white"
+ color_icon_root_button: self.color_icon_stack_button
+
+ .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-color-icon-root-button.png
+ :align: center
:attr:`color_icon_root_button` is a :class:`~kivy.properties.ColorProperty`
- and defaults to `[]`.
+ and defaults to `None`.
"""
bg_hint_color = ColorProperty(None)
"""
- Background color for the text of the buttons in the stack (r, g, b, a) format.
+ Background color for the floating text of the buttons in (r, g, b, a)
+ or string format.
+
+ .. code-clock:: kv
+
+ MDFloatingActionButtonSpeedDial:
+ bg_hint_color: "red"
+ hint_animation: True
+
+ .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-bg-hint-color.png
+ :align: center
:attr:`bg_hint_color` is a :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
@@ -1609,12 +1954,14 @@ class MDFloatingActionButtonSpeedDial(ThemableBehavior, FloatLayout):
hint_animation = BooleanProperty(False)
"""
- Whether to use button extension animation to display text labels.
+ Whether to use button extension animation to display floating text.
:attr:`hint_animation` is a :class:`~kivy.properties.BooleanProperty`
and defaults to `False`.
"""
+ stack_buttons = DictProperty()
+
_label_pos_y_set = False
_anim_buttons_data = {}
_anim_labels_data = {}
@@ -1623,6 +1970,8 @@ class MDFloatingActionButtonSpeedDial(ThemableBehavior, FloatLayout):
super().__init__(**kwargs)
self.register_event_type("on_open")
self.register_event_type("on_close")
+ self.register_event_type("on_press_stack_button")
+ self.register_event_type("on_release_stack_button")
Window.bind(on_resize=self._update_pos_buttons)
def on_open(self, *args):
@@ -1657,19 +2006,22 @@ class MDFloatingActionButtonSpeedDial(ThemableBehavior, FloatLayout):
if self.state == "open":
for widget in self.children:
if isinstance(widget, MDFloatingLabel) and self.hint_animation:
- widget._elevation = 0
Animation.cancel_all(widget)
for item in self.data.items():
if widget.text in item:
Animation(
_canvas_width=widget.width + dp(24),
- _padding_right=dp(5) if self.right_pad else 0,
+ _padding_right=self.right_pad_value
+ if self.right_pad
+ else 0,
d=self.opening_time,
t=self.opening_transition,
).start(instance_button)
if (
instance_button.icon
== self.data[f"{widget.text}"]
+ or instance_button.icon
+ == self.data[f"{widget.text}"][0]
):
Animation(
opacity=1,
@@ -1684,51 +2036,68 @@ class MDFloatingActionButtonSpeedDial(ThemableBehavior, FloatLayout):
def on_data(self, instance_speed_dial, data: dict) -> None:
"""Creates a stack of buttons."""
- # FIXME: Don't know how to fix AttributeError error:
- # File "kivymd/uix/button.py", line 1597, in on_data
- # self.add_widget(bottom_button)
- # File "kivy/uix/floatlayout.py", line 140, in add_widget
- # return super(FloatLayout, self).add_widget(widget, index, canvas)
- # File "kivy/uix/layout.py", line 97, in add_widget
- # return super(Layout, self).add_widget(widget, index, canvas)
- # File "kivy/uix/widget.py", line 629, in add_widget
- # canvas.add(widget.canvas)
- # AttributeError: 'NoneType' object has no attribute 'add'
- super().__init__()
+ def on_data(*args):
+ # Bottom buttons.
+ for name, parameters in data.items():
+ name_icon = (
+ parameters if (type(parameters) is str) else parameters[0]
+ )
+
+ bottom_button = MDFloatingBottomButton(
+ icon=name_icon,
+ on_enter=self.on_enter,
+ on_leave=self.on_leave,
+ opacity=0,
+ )
+ bottom_button.bind(
+ on_press=lambda x: self.dispatch("on_press_stack_button"),
+ on_release=lambda x: self.dispatch(
+ "on_release_stack_button"
+ ),
+ )
+
+ if "on_press" in parameters:
+ callback = parameters[parameters.index("on_press") + 1]
+ bottom_button.bind(on_press=callback)
+
+ if "on_release" in parameters:
+ callback = parameters[parameters.index("on_release") + 1]
+ bottom_button.bind(on_release=callback)
+
+ self.set_pos_bottom_buttons(bottom_button)
+ self.add_widget(bottom_button)
+ self.stack_buttons[name] = WeakProxy(bottom_button)
+ # Labels.
+ floating_text = name
+ if floating_text:
+ label = MDFloatingLabel(text=floating_text, opacity=0)
+ label.bg_color = self.label_bg_color
+ label.radius = self.label_radius
+ label.text_color = (
+ self.label_text_color
+ if self.label_text_color
+ else self.theme_cls.text_color
+ )
+ self.add_widget(label)
+ # Top root button.
+ root_button = MDFloatingRootButton(on_release=self.open_stack)
+ root_button.icon = self.icon
+ self.set_pos_root_button(root_button)
+ self.add_widget(root_button)
+
self.clear_widgets()
+ self.stack_buttons = {}
self._anim_buttons_data = {}
self._anim_labels_data = {}
self._label_pos_y_set = False
-
- # Bottom buttons.
- for name, name_icon in data.items():
- bottom_button = MDFloatingBottomButton(
- icon=name_icon,
- on_enter=self.on_enter,
- on_leave=self.on_leave,
- opacity=0,
- )
- bottom_button.bind(
- on_release=lambda x=bottom_button: self.callback(x)
- )
- self.set_pos_bottom_buttons(bottom_button)
- self.add_widget(bottom_button)
- # Labels.
- floating_text = name
- if floating_text:
- label = MDFloatingLabel(text=floating_text, opacity=0)
- label.text_color = self.label_text_color
- self.add_widget(label)
- # Top root button.
- root_button = MDFloatingRootButton(on_release=self.open_stack)
- root_button.icon = self.icon
- self.set_pos_root_button(root_button)
- self.add_widget(root_button)
+ Clock.schedule_once(on_data)
def on_icon(self, instance_speed_dial, name_icon: str) -> None:
- self._get_count_widget(MDFloatingRootButton).icon = name_icon
+ self._set_button_property(MDFloatingRootButton, "icon", name_icon)
- def on_label_text_color(self, instance_speed_dial, color: list) -> None:
+ def on_label_text_color(
+ self, instance_speed_dial, color: list | str
+ ) -> None:
for widget in self.children:
if isinstance(widget, MDFloatingLabel):
widget.text_color = color
@@ -1736,34 +2105,52 @@ class MDFloatingActionButtonSpeedDial(ThemableBehavior, FloatLayout):
def on_color_icon_stack_button(
self, instance_speed_dial, color: list
) -> None:
- for widget in self.children:
- if isinstance(widget, MDFloatingBottomButton):
- widget.text_color = color
+ self._set_button_property(MDFloatingBottomButton, "icon_color", color)
def on_hint_animation(self, instance_speed_dial, value: bool) -> None:
for widget in self.children:
if isinstance(widget, MDFloatingLabel):
- widget.bg_color = (0, 0, 0, 0)
+ widget.md_bg_color = (0, 0, 0, 0)
def on_bg_hint_color(self, instance_speed_dial, color: list) -> None:
- for widget in self.children:
- if isinstance(widget, MDFloatingBottomButton):
- widget._bg_color = color
+ setattr(MDFloatingBottomButton, "_bg_color", color)
def on_color_icon_root_button(
self, instance_speed_dial, color: list
) -> None:
- self._get_count_widget(MDFloatingRootButton).text_color = color
+ self._set_button_property(MDFloatingRootButton, "icon_color", color)
def on_bg_color_stack_button(
self, instance_speed_dial, color: list
) -> None:
- for widget in self.children:
- if isinstance(widget, MDFloatingBottomButton):
- widget.md_bg_color = color
+ self._set_button_property(MDFloatingBottomButton, "md_bg_color", color)
def on_bg_color_root_button(self, instance_speed_dial, color: list) -> None:
- self._get_count_widget(MDFloatingRootButton).md_bg_color = color
+ self._set_button_property(MDFloatingRootButton, "md_bg_color", color)
+
+ def on_press_stack_button(self, *args) -> None:
+ """
+ Called at the on_press event for the stack button.
+
+ .. code-block:: kv
+
+ MDFloatingActionButtonSpeedDial:
+ on_press_stack_button: print(*args)
+
+ .. versionadded:: 1.1.0
+ """
+
+ def on_release_stack_button(self, *args) -> None:
+ """
+ Called at the on_release event for the stack button.
+
+ .. code-block:: kv
+
+ MDFloatingActionButtonSpeedDial:
+ on_release_stack_button: print(*args)
+
+ .. versionadded:: 1.1.0
+ """
def set_pos_labels(self, instance_floating_label: MDFloatingLabel) -> None:
"""
@@ -1784,9 +2171,14 @@ class MDFloatingActionButtonSpeedDial(ThemableBehavior, FloatLayout):
Called when the application's root window is resized.
"""
- if self.anchor == "right":
- instance_floating_root_button.y = dp(20)
- instance_floating_root_button.x = Window.width - (dp(56) + dp(20))
+ def set_pos_root_button(*args):
+ if self.anchor == "right":
+ instance_floating_root_button.y = dp(20)
+ instance_floating_root_button.x = self.parent.width - (
+ dp(56) + dp(20)
+ )
+
+ Clock.schedule_once(set_pos_root_button)
def set_pos_bottom_buttons(
self, instance_floating_bottom_button: MDFloatingBottomButton
@@ -1817,7 +2209,7 @@ class MDFloatingActionButtonSpeedDial(ThemableBehavior, FloatLayout):
if self.state != "open":
y = 0
- label_position = dp(56)
+ label_position = dp(54)
anim_buttons_data = {}
anim_labels_data = {}
@@ -1849,7 +2241,7 @@ class MDFloatingActionButtonSpeedDial(ThemableBehavior, FloatLayout):
):
# Rotates the root button 45 degrees.
Animation(
- _angle=-45,
+ rotate_value_angle=-45,
d=self.opening_time_button_rotation,
t=self.opening_transition_button_rotation,
).start(widget)
@@ -1908,13 +2300,14 @@ class MDFloatingActionButtonSpeedDial(ThemableBehavior, FloatLayout):
opacity=0,
).start(widget)
elif isinstance(widget, MDFloatingLabel):
- Animation(opacity=0, d=0.1).start(widget)
+ if widget.opacity > 0:
+ Animation(opacity=0, d=0.1).start(widget)
elif (
isinstance(widget, MDFloatingRootButton)
and self.root_button_anim
):
Animation(
- _angle=0,
+ rotate_value_angle=0,
d=self.closing_time_button_rotation,
t=self.closing_transition_button_rotation,
).start(widget)
@@ -1931,9 +2324,15 @@ class MDFloatingActionButtonSpeedDial(ThemableBehavior, FloatLayout):
elif isinstance(widget, MDFloatingLabel):
self.set_pos_labels(widget)
- def _get_count_widget(self, instance):
- widget = None
- for widget in self.children:
- if isinstance(widget, instance):
- break
- return widget
+ def _set_button_property(
+ self, instance, property_name: str, property_value: str | list
+ ):
+ def set_count_widget(*args):
+ if self.children:
+ for widget in self.children:
+ if isinstance(widget, instance):
+ setattr(instance, property_name, property_value)
+ Clock.unschedule(set_count_widget)
+ break
+
+ Clock.schedule_interval(set_count_widget, 0)
diff --git a/sbapp/kivymd/uix/card/card.kv b/sbapp/kivymd/uix/card/card.kv
index ac67a51c..7ed37bd7 100644
--- a/sbapp/kivymd/uix/card/card.kv
+++ b/sbapp/kivymd/uix/card/card.kv
@@ -2,17 +2,6 @@
md_bg_color: app.theme_cls.divider_color
-<MDCard>
- canvas.before:
- Color:
- rgba: self.md_bg_color
- RoundedRectangle:
- size: self.size
- pos: self.pos
- radius: root.radius
- source: root.background
-
-
<MDSeparator>
md_bg_color:
self.theme_cls.divider_color \
diff --git a/sbapp/kivymd/uix/card/card.py b/sbapp/kivymd/uix/card/card.py
index 53e18c06..d224ae18 100755
--- a/sbapp/kivymd/uix/card/card.py
+++ b/sbapp/kivymd/uix/card/card.py
@@ -26,26 +26,6 @@ Components/Card
MDCard
------
-.. warning:: Starting from the KivyMD 1.1.0 library version, it is necessary
- to manually inherit the card class from one of the ``Elevation`` classes
- from ``kivymd/uix/behaviors/elevation.py`` module to draw the card shadow.
-
-.. code-block:: python
-
- from kivymd.uix.behaviors import RoundedRectangularElevationBehavior
- from kivymd.uix.card import MDCard
-
-
- class MD3Card(MDCard, RoundedRectangularElevationBehavior):
- '''Implements a material design v3 card.'''
-
-It actually allows for better control over the providers that implement the
-rendering of the shadows.
-
-.. note:: You can read more information about the classes that implement the
- rendering of shadows on this
- `documentation page <https://kivymd.readthedocs.io/en/latest/behaviors/elevation/>`_.
-
An example of the implementation of a card in the style of material design version 3
------------------------------------------------------------------------------------
@@ -59,7 +39,6 @@ An example of the implementation of a card in the style of material design versi
from kivy.properties import StringProperty
from kivymd.app import MDApp
- from kivymd.uix.behaviors import RoundedRectangularElevationBehavior
from kivymd.uix.card import MDCard
KV = '''
@@ -93,7 +72,7 @@ An example of the implementation of a card in the style of material design versi
'''
- class MD3Card(MDCard, RoundedRectangularElevationBehavior):
+ class MD3Card(MDCard):
'''Implements a material design v3 card.'''
text = StringProperty()
@@ -126,7 +105,6 @@ An example of the implementation of a card in the style of material design versi
.. code-block:: python
from kivymd.app import MDApp
- from kivymd.uix.behaviors import RoundedRectangularElevationBehavior
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.button import MDIconButton
from kivymd.uix.card import MDCard
@@ -135,7 +113,7 @@ An example of the implementation of a card in the style of material design versi
from kivymd.uix.screen import MDScreen
- class MD3Card(MDCard, RoundedRectangularElevationBehavior):
+ class MD3Card(MDCard):
'''Implements a material design v3 card.'''
@@ -170,7 +148,6 @@ An example of the implementation of a card in the style of material design versi
adaptive_size=True,
color="grey",
pos=("12dp", "12dp"),
- bold=True,
),
),
line_color=(0.2, 0.2, 0.2, 0.8),
@@ -255,10 +232,9 @@ End full code
MDBoxLayout:
orientation: "vertical"
- spacing: "10dp"
MDTopAppBar:
- elevation: 10
+ elevation: 4
title: "MDCardSwipe"
MDScrollView:
@@ -286,7 +262,7 @@ End full code
'''Creates a list of cards.'''
for i in range(20):
- self.screen.ids.md_list.add_widget(
+ self.root.ids.md_list.add_widget(
SwipeToDeleteItem(text=f"One-line item {i}")
)
@@ -316,7 +292,7 @@ End full code
MDScreen(
MDBoxLayout(
MDTopAppBar(
- elevation=10,
+ elevation=4,
title="MDCardSwipe",
),
MDScrollView(
@@ -328,7 +304,6 @@ End full code
),
id="box",
orientation="vertical",
- spacing="10dp",
),
)
)
@@ -426,7 +401,7 @@ You can use this event to remove items from a list:
.. code-block:: python
def on_swipe_complete(self, instance):
- self.screen.ids.md_list.remove_widget(instance)
+ self.root.ids.md_list.remove_widget(instance)
.. tab:: Decralative python styles
@@ -496,10 +471,9 @@ End full code
MDBoxLayout:
orientation: "vertical"
- spacing: "10dp"
MDTopAppBar:
- elevation: 10
+ elevation: 4
title: "MDCardSwipe"
MDScrollView:
@@ -560,7 +534,7 @@ End full code
MDScreen(
MDBoxLayout(
MDTopAppBar(
- elevation=10,
+ elevation=4,
title="MDCardSwipe",
),
MDScrollView(
@@ -572,7 +546,6 @@ End full code
),
id="box",
orientation="vertical",
- spacing="10dp",
),
)
)
@@ -630,26 +603,21 @@ Focus behavior
from kivy.lang import Builder
from kivymd.app import MDApp
- from kivymd.uix.behaviors import FakeRectangularElevationBehavior
- from kivymd.uix.card import MDCard
KV = '''
MDScreen:
- ElevationCard:
+ MDCard:
size_hint: .7, .4
focus_behavior: True
pos_hint: {"center_x": .5, "center_y": .5}
md_bg_color: "darkgrey"
unfocus_color: "darkgrey"
focus_color: "grey"
+ elevation: 6
'''
- class ElevationCard(MDCard, FakeRectangularElevationBehavior):
- pass
-
-
class Example(MDApp):
def build(self):
self.theme_cls.theme_style = "Dark"
@@ -663,27 +631,23 @@ Focus behavior
.. code-block:: python
from kivymd.app import MDApp
- from kivymd.uix.behaviors import FakeRectangularElevationBehavior
from kivymd.uix.card import MDCard
from kivymd.uix.screen import MDScreen
- class ElevationCard(MDCard, FakeRectangularElevationBehavior):
- pass
-
-
class Example(MDApp):
def build(self):
self.theme_cls.theme_style = "Dark"
return (
MDScreen(
- ElevationCard(
+ MDCard(
size_hint=(0.7, 0.4),
focus_behavior=True,
pos_hint={"center_x": 0.5, "center_y": 0.5},
md_bg_color="darkgrey",
unfocus_color="darkgrey",
focus_color="grey",
+ elevation=6,
),
)
)
@@ -691,7 +655,6 @@ Focus behavior
Example().run()
-
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/card-focus.gif
:align: center
@@ -732,12 +695,14 @@ from kivy.properties import (
VariableListProperty,
)
from kivy.uix.boxlayout import BoxLayout
+from kivy.utils import get_color_from_hex
from kivymd import uix_path
from kivymd.color_definitions import colors
from kivymd.theming import ThemableBehavior
from kivymd.uix.behaviors import (
BackgroundColorBehavior,
+ CommonElevationBehavior,
DeclarativeBehavior,
RectangularRippleBehavior,
)
@@ -781,6 +746,7 @@ class MDCard(
ThemableBehavior,
BackgroundColorBehavior,
RectangularRippleBehavior,
+ CommonElevationBehavior,
FocusBehavior,
BoxLayout,
):
@@ -800,14 +766,6 @@ class MDCard(
and defaults to `False`.
"""
- elevation = NumericProperty(None, allownone=True)
- """
- Elevation value.
-
- :attr:`elevation` is an :class:`~kivy.properties.NumericProperty`
- and defaults to 1.
- """
-
radius = VariableListProperty([dp(6), dp(6), dp(6), dp(6)])
"""
Card radius by default.
@@ -831,15 +789,16 @@ class MDCard(
"""
_bg_color_map = (
- colors["Light"]["CardsDialogs"],
- colors["Dark"]["CardsDialogs"],
+ get_color_from_hex(colors["Light"]["CardsDialogs"]),
+ get_color_from_hex(colors["Dark"]["CardsDialogs"]),
[1.0, 1.0, 1.0, 0.0],
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
- self.theme_cls.bind(theme_style=self.update_md_bg_color)
- self.theme_cls.bind(material_style=self.set_style)
+ self.theme_cls.bind(
+ material_style=self.set_style, theme_style=self.update_md_bg_color
+ )
Clock.schedule_once(self.set_style)
Clock.schedule_once(
lambda x: self.on_ripple_behavior(0, self.ripple_behavior)
@@ -848,7 +807,9 @@ class MDCard(
def update_md_bg_color(self, instance_card, theme_style: str) -> None:
if self.md_bg_color in self._bg_color_map:
- self.md_bg_color = colors[theme_style]["CardsDialogs"]
+ self.md_bg_color = get_color_from_hex(
+ colors[theme_style]["CardsDialogs"]
+ )
def set_style(self, *args) -> None:
self.set_radius()
@@ -865,7 +826,7 @@ class MDCard(
if self.style == "outlined" or self.style == "filled":
self.elevation = 0
elif self.style == "elevated":
- self.elevation = 1
+ self.elevation = 2
def set_radius(self) -> None:
if (
diff --git a/sbapp/kivymd/uix/chip/chip.py b/sbapp/kivymd/uix/chip/chip.py
index 433f242f..03e1a98d 100755
--- a/sbapp/kivymd/uix/chip/chip.py
+++ b/sbapp/kivymd/uix/chip/chip.py
@@ -132,7 +132,7 @@ Use with elevation
icon_right: "close-circle-outline"
line_color: app.theme_cls.disabled_hint_text_color
md_bg_color: 1, 0, 0, .5
- elevation: 12
+ elevation: 4
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/chip-with-elevation.png
:align: center
@@ -304,7 +304,6 @@ __all__ = ("MDChip",)
import os
-from kivy import Logger
from kivy.animation import Animation
from kivy.lang import Builder
from kivy.metrics import dp
@@ -314,14 +313,13 @@ from kivy.uix.behaviors import ButtonBehavior
from kivymd import uix_path
from kivymd.theming import ThemableBehavior
from kivymd.uix.behaviors import (
- FakeRectangularElevationBehavior,
+ CommonElevationBehavior,
RectangularRippleBehavior,
+ ScaleBehavior,
TouchBehavior,
)
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.label import MDIcon
-from kivymd.uix.stacklayout import MDStackLayout
-from kivymd.uix.templates import ScaleWidget
with open(
os.path.join(uix_path, "chip", "chip.kv"), encoding="utf-8"
@@ -330,12 +328,12 @@ with open(
class MDChip(
+ MDBoxLayout,
ThemableBehavior,
RectangularRippleBehavior,
- FakeRectangularElevationBehavior,
- TouchBehavior,
ButtonBehavior,
- MDBoxLayout,
+ CommonElevationBehavior,
+ TouchBehavior,
):
text = StringProperty()
"""
@@ -456,7 +454,7 @@ class MDChip(
self.active = False
-class MDScalableCheckIcon(MDIcon, ScaleWidget):
+class MDScalableCheckIcon(MDIcon, ScaleBehavior):
pos_hint = {"center_y": 0.5}
diff --git a/sbapp/kivymd/uix/datatables/datatables.kv b/sbapp/kivymd/uix/datatables/datatables.kv
index 188ddde3..a6a72201 100644
--- a/sbapp/kivymd/uix/datatables/datatables.kv
+++ b/sbapp/kivymd/uix/datatables/datatables.kv
@@ -1,5 +1,4 @@
#:import DEVICE_TYPE kivymd.material_resources.DEVICE_TYPE
-#:import FakeRectangularElevationBehavior kivymd.uix.behaviors.FakeRectangularElevationBehavior
<CellRow>
@@ -66,7 +65,7 @@
size_hint_y: None
height: self.minimum_height
spacing: "4dp"
- tooltip_text: root.text
+ tooltip_text: root.tooltip if root.tooltip else root.text
BoxLayout:
id: box
@@ -175,7 +174,11 @@
font_size: "14sp"
on_release: root.table_data.open_pagination_menu()
text:
- f"{root.table_data.rows_num if root.table_data.rows_num < len(root.table_data.row_data) else len(root.table_data.row_data)}"
+ "{}".format( \
+ root.table_data.rows_num \
+ if root.table_data.rows_num < len(root.table_data.row_data) else \
+ len(root.table_data.row_data) \
+ )
Widget:
size_hint_x: None
@@ -192,9 +195,11 @@
if root.theme_cls.theme_style == "Dark" else \
(0, 0, 0, 1)
text:
- f"1-" \
- f"{root.table_data.rows_num if root.table_data.rows_num > len(root.table_data.row_data) else len(root.table_data.row_data)} " \
- f"of {len(root.table_data.row_data)}"
+ "1-{} of {}".format( \
+ root.table_data.rows_num \
+ if root.table_data.rows_num > len(root.table_data.row_data) else \
+ len(root.table_data.row_data), len(root.table_data.row_data) \
+ )
MDIconButton:
id: button_back
@@ -217,7 +222,7 @@
on_release: root.table_data.set_next_row_data_parts("forward")
-<TableContainer@MDCard+FakeRectangularElevationBehavior>
+<TableContainer@MDCard>
<MDDataTable>
diff --git a/sbapp/kivymd/uix/datatables/datatables.py b/sbapp/kivymd/uix/datatables/datatables.py
index b574d060..16262838 100644
--- a/sbapp/kivymd/uix/datatables/datatables.py
+++ b/sbapp/kivymd/uix/datatables/datatables.py
@@ -11,19 +11,6 @@ Components/DataTables
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-previous.png
:align: center
-Warnings
----------
-
-.. warning:: Data tables are still far from perfect. The class is in constant
- change, because of optimizations and bug fixes. If you find a bug or have
- an improvement you want to share, take some time and share your discoveries
- with us over the main git repo.
- Any help is well appreciated.
-
-.. warning:: In versions prior to `Kivy 2.1.0-dev0` exists an error in which is
- the table has only one row in the current page, the table will only render
- one column instead of the whole row.
-
.. note:: `MDDataTable` allows developers to sort the data provided by column.
This happens thanks to the use of an external function that you can bind
while you're defining the table columns. Be aware that the sorting function
@@ -159,6 +146,15 @@ class CellHeader(MDTooltip, BoxLayout):
and defaults to `''`.
"""
+ tooltip = StringProperty()
+ """
+ Tooltip containing descriptive text for the column.
+ If the tooltip is not provided, column `text` shall be used instead.
+
+ :attr:`tooltip` is a :class:`~kivy.properties.StringProperty`
+ and defaults to `''`.
+ """
+
# TODO: Added example.
sort_action = ObjectProperty()
"""
@@ -340,11 +336,19 @@ class TableHeader(ThemableBehavior, ScrollView):
CellHeader(
text=col_heading[0],
sort_action=col_heading[2],
+ tooltip=col_heading[3],
width=self.cols_minimum[i],
table_data=self.table_data,
is_sorted=(col_heading[0] == self.sorted_on),
sorted_order=self.sorted_order,
)
+ if len(col_heading) == 4
+ else CellHeader(
+ text=col_heading[0],
+ sort_action=col_heading[2],
+ width=self.cols_minimum[i],
+ table_data=self.table_data,
+ )
if len(col_heading) == 3
else CellHeader(
text=col_heading[0],
@@ -356,6 +360,9 @@ class TableHeader(ThemableBehavior, ScrollView):
else:
# Sets the text in the first cell.
self.ids.first_cell.text = col_heading[0]
+ self.ids.first_cell.tooltip = (
+ col_heading[3] if len(col_heading) == 4 else ""
+ )
self.ids.first_cell.ids.separator.height = 0
self.ids.first_cell.width = self.cols_minimum[i]
@@ -765,6 +772,9 @@ class TablePagination(ThemableBehavior, MDBoxLayout):
class MDDataTable(ThemableBehavior, AnchorLayout):
"""
+ See :class:`~kivy.uix.anchorlayout.AnchorLayout` class documentation for
+ more information.
+
:Events:
:attr:`on_row_press`
Called when a table row is clicked.
@@ -775,7 +785,6 @@ class MDDataTable(ThemableBehavior, AnchorLayout):
.. code-block:: python
-
from kivy.metrics import dp
from kivymd.app import MDApp
@@ -914,38 +923,82 @@ class MDDataTable(ThemableBehavior, AnchorLayout):
"""
Data for header columns.
- .. code-block:: python
+ .. tabs::
- from kivy.metrics import dp
+ .. tab:: Imperative python style
- from kivymd.app import MDApp
- from kivymd.uix.datatables import MDDataTable
- from kivy.uix.anchorlayout import AnchorLayout
+ .. code-block:: python
+ from kivy.metrics import dp
- class Example(MDApp):
- def build(self):
- layout = AnchorLayout()
- self.data_tables = MDDataTable(
- size_hint=(0.7, 0.6),
- use_pagination=True,
- check=True,
- # name column, width column, sorting function column(optional)
- column_data=[
- ("No.", dp(30)),
- ("Status", dp(30)),
- ("Signal Name", dp(60)),
- ("Severity", dp(30)),
- ("Stage", dp(30)),
- ("Schedule", dp(30), lambda *args: print("Sorted using Schedule")),
- ("Team Lead", dp(30)),
- ],
- )
- layout.add_widget(self.data_tables)
- return layout
+ from kivymd.app import MDApp
+ from kivymd.uix.datatables import MDDataTable
+ from kivy.uix.anchorlayout import AnchorLayout
- Example().run()
+ class Example(MDApp):
+ def build(self):
+ self.theme_cls.theme_style = "Dark"
+ self.theme_cls.primary_palette = "Orange"
+
+ layout = AnchorLayout()
+ self.data_tables = MDDataTable(
+ size_hint=(0.7, 0.6),
+ use_pagination=True,
+ check=True,
+ # name column, width column, sorting function column(optional), custom tooltip
+ column_data=[
+ ("No.", dp(30), None, "Custom tooltip"),
+ ("Status", dp(30)),
+ ("Signal Name", dp(60)),
+ ("Severity", dp(30)),
+ ("Stage", dp(30)),
+ ("Schedule", dp(30), lambda *args: print("Sorted using Schedule")),
+ ("Team Lead", dp(30)),
+ ],
+ )
+ layout.add_widget(self.data_tables)
+ return layout
+
+
+ Example().run()
+
+ .. tab:: Declarative python style
+
+ .. code-block:: python
+
+ from kivy.metrics import dp
+
+ from kivymd.app import MDApp
+ from kivymd.uix.anchorlayout import MDAnchorLayout
+ from kivymd.uix.datatables import MDDataTable
+
+
+ class Example(MDApp):
+ def build(self):
+ self.theme_cls.theme_style = "Dark"
+ self.theme_cls.primary_palette = "Orange"
+ return MDAnchorLayout(
+ MDDataTable(
+ size_hint=(0.7, 0.6),
+ use_pagination=True,
+ check=True,
+ # name column, width column, sorting function column(optional)
+ column_data=[
+ ("No.", dp(30)),
+ ("Status", dp(30)),
+ ("Signal Name", dp(60)),
+ ("Severity", dp(30)),
+ ("Stage", dp(30)),
+ ("Schedule", dp(30),
+ lambda *args: print("Sorted using Schedule")),
+ ("Team Lead", dp(30)),
+ ],
+ )
+ )
+
+
+ Example().run()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-column-data.png
:align: center
@@ -1060,6 +1113,9 @@ class MDDataTable(ThemableBehavior, AnchorLayout):
class Example(MDApp):
def build(self):
+ self.theme_cls.theme_style = "Dark"
+ self.theme_cls.primary_palette = "Orange"
+
layout = AnchorLayout()
data_tables = MDDataTable(
size_hint=(0.9, 0.6),
@@ -1187,7 +1243,7 @@ class MDDataTable(ThemableBehavior, AnchorLayout):
"""
Use or not use checkboxes for rows.
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-check.gif
+ .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-check.png
:align: center
:attr:`check` is an :class:`~kivy.properties.BooleanProperty`
@@ -1209,6 +1265,9 @@ class MDDataTable(ThemableBehavior, AnchorLayout):
class Example(MDApp):
def build(self):
+ self.theme_cls.theme_style = "Dark"
+ self.theme_cls.primary_palette = "Orange"
+
layout = AnchorLayout()
data_tables = MDDataTable(
size_hint=(0.9, 0.6),
@@ -1238,19 +1297,19 @@ class MDDataTable(ThemableBehavior, AnchorLayout):
and defaults to `False`.
"""
- elevation = NumericProperty(8)
+ elevation = NumericProperty(4)
"""
Table elevation.
:attr:`elevation` is an :class:`~kivy.properties.NumericProperty`
- and defaults to `8`.
+ and defaults to `4`.
"""
rows_num = NumericProperty(5)
"""
The number of rows displayed on one page of the table.
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-use-pagination.gif
+ .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-use-pagination-rows-num.png
:align: center
:attr:`rows_num` is an :class:`~kivy.properties.NumericProperty`
@@ -1266,7 +1325,7 @@ class MDDataTable(ThemableBehavior, AnchorLayout):
.. rubric:: Center
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-menu-pos-center.png
+ .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-menu-pos-top.png
:align: center
.. rubric:: Auto
@@ -1282,11 +1341,6 @@ class MDDataTable(ThemableBehavior, AnchorLayout):
"""
Menu height for selecting the number of displayed rows.
- .. rubric:: 140dp
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-menu-height-140.png
- :align: center
-
.. rubric:: 240dp
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-menu-height-240.png
@@ -1298,7 +1352,7 @@ class MDDataTable(ThemableBehavior, AnchorLayout):
background_color = ColorProperty([0, 0, 0, 0])
"""
- Background color in the format (r, g, b, a).
+ Background color in the format (r, g, b, a) or string format.
See :attr:`~kivy.uix.modalview.ModalView.background_color`.
Use markup strings
@@ -1315,6 +1369,9 @@ class MDDataTable(ThemableBehavior, AnchorLayout):
class Example(MDApp):
def build(self):
+ self.theme_cls.theme_style = "Dark"
+ self.theme_cls.primary_palette = "Orange"
+
layout = AnchorLayout()
data_tables = MDDataTable(
size_hint=(0.9, 0.6),
@@ -1354,7 +1411,8 @@ class MDDataTable(ThemableBehavior, AnchorLayout):
background_color_header = ColorProperty(None)
"""
- Background color for :class:`~TableHeader` class.
+ Background color in the format (r, g, b, a) or string format for
+ :class:`~TableHeader` class.
.. versionadded:: 1.0.0
@@ -1374,7 +1432,8 @@ class MDDataTable(ThemableBehavior, AnchorLayout):
background_color_cell = ColorProperty(None)
"""
- Background color for :class:`~CellRow` class.
+ Background color in the format (r, g, b, a) or string format for
+ :class:`~CellRow` class.
.. versionadded:: 1.0.0
@@ -1395,7 +1454,8 @@ class MDDataTable(ThemableBehavior, AnchorLayout):
background_color_selected_cell = ColorProperty(None)
"""
- Background selected color for :class:`~CellRow` class.
+ Background selected color in the format (r, g, b, a) or string format for
+ :class:`~CellRow` class.
.. versionadded:: 1.0.0
@@ -1408,7 +1468,7 @@ class MDDataTable(ThemableBehavior, AnchorLayout):
background_color_selected_cell="e4514f",
)
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-background-color-selected-cell.gif
+ .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-background-color-selected-cell.png
:align: center
:attr:`background_color_selected_cell` is a :class:`~kivy.properties.ColorProperty` and
@@ -1503,6 +1563,9 @@ class MDDataTable(ThemableBehavior, AnchorLayout):
data_tables = None
def build(self):
+ self.theme_cls.theme_style = "Dark"
+ self.theme_cls.primary_palette = "Orange"
+
layout = MDFloatLayout() # root layout
# Creating control buttons.
button_box = MDBoxLayout(
@@ -1604,6 +1667,9 @@ class MDDataTable(ThemableBehavior, AnchorLayout):
data_tables = None
def build(self):
+ self.theme_cls.theme_style = "Dark"
+ self.theme_cls.primary_palette = "Orange"
+
layout = MDFloatLayout()
layout.add_widget(
MDRaisedButton(
diff --git a/sbapp/kivymd/uix/dialog/dialog.kv b/sbapp/kivymd/uix/dialog/dialog.kv
index 1f057a72..96e01a37 100644
--- a/sbapp/kivymd/uix/dialog/dialog.kv
+++ b/sbapp/kivymd/uix/dialog/dialog.kv
@@ -18,7 +18,11 @@
PopMatrix
-<DialogContainer@MDCard+FakeRectangularElevationBehavior>
+<DialogContainer@MDCard>
+ shadow_color: 0.0, 0.0, 0.0, 0.0
+ elevation: 0
+ shadow_softness: 0
+ shadow_offset: 0, 0
<MDDialog>
@@ -28,7 +32,6 @@
orientation: "vertical"
size_hint_y: None
height: self.minimum_height
- elevation: 24
padding: "24dp", "24dp", "8dp", "8dp"
radius: root.radius
md_bg_color:
diff --git a/sbapp/kivymd/uix/dialog/dialog.py b/sbapp/kivymd/uix/dialog/dialog.py
index 10f4d671..f254f308 100755
--- a/sbapp/kivymd/uix/dialog/dialog.py
+++ b/sbapp/kivymd/uix/dialog/dialog.py
@@ -38,6 +38,8 @@ Usage
dialog = None
def build(self):
+ self.theme_cls.theme_style = "Dark"
+ self.theme_cls.primary_palette = "Orange"
return Builder.load_string(KV)
def show_alert_dialog(self):
@@ -87,6 +89,7 @@ from kivy.uix.modalview import ModalView
from kivymd import uix_path
from kivymd.material_resources import DEVICE_TYPE
from kivymd.theming import ThemableBehavior
+from kivymd.uix.behaviors import CommonElevationBehavior
from kivymd.uix.button import BaseButton
from kivymd.uix.card import MDSeparator
from kivymd.uix.list import BaseListItem
@@ -97,7 +100,40 @@ with open(
Builder.load_string(kv_file.read())
-class BaseDialog(ThemableBehavior, ModalView):
+class BaseDialog(ThemableBehavior, ModalView, CommonElevationBehavior):
+ elevation = NumericProperty(3)
+ """
+ See :attr:`kivymd.uix.behaviors.elevation.CommonElevationBehavior.elevation`
+ attribute for more information.
+
+ .. versionadded:: 1.1.0
+
+ :attr:`elevation` is an :class:`~kivy.properties.NumericProperty`
+ and defaults to `3`.
+ """
+
+ shadow_softness = NumericProperty(24)
+ """
+ See :attr:`kivymd.uix.behaviors.elevation.CommonElevationBehavior.shadow_softness`
+ attribute for more information.
+
+ .. versionadded:: 1.1.0
+
+ :attr:`shadow_softness` is an :class:`~kivy.properties.NumericProperty`
+ and defaults to `24`.
+ """
+
+ shadow_offset = ListProperty((0, 4))
+ """
+ See :attr:`kivymd.uix.behaviors.elevation.CommonElevationBehavior.shadow_offset`
+ attribute for more information.
+
+ .. versionadded:: 1.1.0
+
+ :attr:`shadow_offset` is an :class:`~kivy.properties.ListProperty`
+ and defaults to `[0, 4]`.
+ """
+
radius = ListProperty([dp(7), dp(7), dp(7), dp(7)])
"""
Dialog corners rounding value.
@@ -250,21 +286,22 @@ class MDDialog(BaseDialog):
class Example(MDApp):
dialog = None
- def build(self):
- return Builder.load_string(KV)
+ def build(self):
+ self.theme_cls.theme_style = "Dark"
+ self.theme_cls.primary_palette = "Orange"
+ return Builder.load_string(KV)
- def show_simple_dialog(self):
- if not self.dialog:
- self.dialog = MDDialog(
- title="Set backup account",
- type="simple",
- items=[
- Item(text="user01@gmail.com", source="user-1.png"),
- Item(text="user02@gmail.com", source="user-2.png"),
- Item(text="Add account", source="add-icon.png"),
- ],
- )
- self.dialog.open()
+ def show_simple_dialog(self):
+ if not self.dialog:
+ self.dialog = MDDialog(
+ title="Set backup account",
+ type="simple",
+ items=[
+ Item(text="user01@gmail.com", source="kivymd/images/logo/kivymd-icon-128.png"),
+ Item(text="user02@gmail.com", source="data/logo/kivy-icon-128.png"),
+ ],
+ )
+ self.dialog.open()
Example().run()
@@ -317,6 +354,8 @@ class MDDialog(BaseDialog):
dialog = None
def build(self):
+ self.theme_cls.theme_style = "Dark"
+ self.theme_cls.primary_palette = "Orange"
return Builder.load_string(KV)
def show_confirmation_dialog(self):
@@ -385,71 +424,140 @@ class MDDialog(BaseDialog):
"""
Custom content class.
- .. code-block:: python
-
- from kivy.lang import Builder
- from kivy.uix.boxlayout import BoxLayout
-
- from kivymd.app import MDApp
- from kivymd.uix.button import MDFlatButton
- from kivymd.uix.dialog import MDDialog
-
- KV = '''
- <Content>
- orientation: "vertical"
- spacing: "12dp"
- size_hint_y: None
- height: "120dp"
+ .. tabs::
- MDTextField:
- hint_text: "City"
+ .. tab:: Declarative KV style
- MDTextField:
- hint_text: "Street"
+ .. code-block:: python
-
- MDFloatLayout:
-
- MDFlatButton:
- text: "ALERT DIALOG"
- pos_hint: {'center_x': .5, 'center_y': .5}
- on_release: app.show_confirmation_dialog()
- '''
-
-
- class Content(BoxLayout):
- pass
-
-
- class Example(MDApp):
- dialog = None
-
- def build(self):
- return Builder.load_string(KV)
-
- def show_confirmation_dialog(self):
- if not self.dialog:
- self.dialog = MDDialog(
- title="Address:",
- type="custom",
- content_cls=Content(),
- buttons=[
- MDFlatButton(
- text="CANCEL",
- theme_text_color="Custom",
- text_color=self.theme_cls.primary_color,
- ),
- MDFlatButton(
- text="OK",
- theme_text_color="Custom",
- text_color=self.theme_cls.primary_color,
- ),
- ],
- )
- self.dialog.open()
-
-
- Example().run()
+ from kivy.lang import Builder
+ from kivy.uix.boxlayout import BoxLayout
+
+ from kivymd.app import MDApp
+ from kivymd.uix.button import MDFlatButton
+ from kivymd.uix.dialog import MDDialog
+
+ KV = '''
+ <Content>
+ orientation: "vertical"
+ spacing: "12dp"
+ size_hint_y: None
+ height: "120dp"
+
+ MDTextField:
+ hint_text: "City"
+
+ MDTextField:
+ hint_text: "Street"
+
+
+ MDFloatLayout:
+
+ MDFlatButton:
+ text: "ALERT DIALOG"
+ pos_hint: {'center_x': .5, 'center_y': .5}
+ on_release: app.show_confirmation_dialog()
+ '''
+
+
+ class Content(BoxLayout):
+ pass
+
+
+ class Example(MDApp):
+ dialog = None
+
+ def build(self):
+ self.theme_cls.theme_style = "Dark"
+ self.theme_cls.primary_palette = "Orange"
+ return Builder.load_string(KV)
+
+ def show_confirmation_dialog(self):
+ if not self.dialog:
+ self.dialog = MDDialog(
+ title="Address:",
+ type="custom",
+ content_cls=Content(),
+ buttons=[
+ MDFlatButton(
+ text="CANCEL",
+ theme_text_color="Custom",
+ text_color=self.theme_cls.primary_color,
+ ),
+ MDFlatButton(
+ text="OK",
+ theme_text_color="Custom",
+ text_color=self.theme_cls.primary_color,
+ ),
+ ],
+ )
+ self.dialog.open()
+
+
+ Example().run()
+
+ .. tab:: Declarative Python style
+
+ .. code-block:: python
+
+ from kivymd.app import MDApp
+ from kivymd.uix.boxlayout import MDBoxLayout
+ from kivymd.uix.button import MDFlatButton
+ from kivymd.uix.dialog import MDDialog
+ from kivymd.uix.floatlayout import MDFloatLayout
+ from kivymd.uix.textfield import MDTextField
+
+
+ class Example(MDApp):
+ dialog = None
+
+ def build(self):
+ self.theme_cls.theme_style = "Dark"
+ self.theme_cls.primary_palette = "Orange"
+ return (
+ MDFloatLayout(
+ MDFlatButton(
+ text="ALERT DIALOG",
+ pos_hint={'center_x': 0.5, 'center_y': 0.5},
+ on_release=self.show_confirmation_dialog,
+ )
+ )
+ )
+
+ def show_confirmation_dialog(self, *args):
+ if not self.dialog:
+ self.dialog = MDDialog(
+ title="Address:",
+ type="custom",
+ content_cls=MDBoxLayout(
+ MDTextField(
+ hint_text="City",
+ ),
+ MDTextField(
+ hint_text="Street",
+ ),
+ orientation="vertical",
+ spacing="12dp",
+ size_hint_y=None,
+ height="120dp",
+ ),
+ buttons=[
+ MDFlatButton(
+ text="CANCEL",
+ theme_text_color="Custom",
+ text_color=self.theme_cls.primary_color,
+ ),
+ MDFlatButton(
+ text="OK",
+ theme_text_color="Custom",
+ text_color=self.theme_cls.primary_color,
+ ),
+ ],
+ )
+ self.dialog.open()
+
+
+ Example().run()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dialog-custom.png
:align: center
@@ -460,7 +568,7 @@ class MDDialog(BaseDialog):
md_bg_color = ColorProperty(None)
"""
- Background color in the format (r, g, b, a).
+ Background color in the (r, g, b, a) or string format.
:attr:`md_bg_color` is an :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
diff --git a/sbapp/kivymd/uix/dropdownitem/dropdownitem.kv b/sbapp/kivymd/uix/dropdownitem/dropdownitem.kv
index 4e6d1635..82960e34 100644
--- a/sbapp/kivymd/uix/dropdownitem/dropdownitem.kv
+++ b/sbapp/kivymd/uix/dropdownitem/dropdownitem.kv
@@ -1,7 +1,7 @@
<_Triangle>:
canvas:
Color:
- rgba: root.theme_cls.text_color
+ rgba: app.theme_cls.text_color
Triangle:
points:
[ \
@@ -13,7 +13,8 @@
<MDDropDownItem>
orientation: "vertical"
- adaptive_size: True
+ size_hint: None, None
+ size: self.minimum_size
spacing: "5dp"
padding: "5dp", "5dp", "5dp", 0
diff --git a/sbapp/kivymd/uix/dropdownitem/dropdownitem.py b/sbapp/kivymd/uix/dropdownitem/dropdownitem.py
index e8a98fbe..3967c5cc 100644
--- a/sbapp/kivymd/uix/dropdownitem/dropdownitem.py
+++ b/sbapp/kivymd/uix/dropdownitem/dropdownitem.py
@@ -15,13 +15,13 @@ Usage
from kivymd.app import MDApp
KV = '''
- Screen
+ MDScreen
MDDropDownItem:
id: drop_item
pos_hint: {'center_x': .5, 'center_y': .5}
text: 'Item'
- on_release: self.set_item("New Item")
+ on_release: print("Press item")
'''
@@ -48,12 +48,12 @@ import os
from kivy.lang import Builder
from kivy.properties import NumericProperty, StringProperty
from kivy.uix.behaviors import ButtonBehavior
+from kivy.uix.boxlayout import BoxLayout
from kivy.uix.widget import Widget
from kivymd import uix_path
from kivymd.theming import ThemableBehavior
-from kivymd.uix.behaviors import FakeRectangularElevationBehavior
-from kivymd.uix.boxlayout import MDBoxLayout
+from kivymd.uix.behaviors import DeclarativeBehavior
with open(
os.path.join(uix_path, "dropdownitem", "dropdownitem.kv"), encoding="utf-8"
@@ -61,15 +61,12 @@ with open(
Builder.load_string(kv_file.read())
-class _Triangle(ThemableBehavior, Widget):
+class _Triangle(Widget):
pass
class MDDropDownItem(
- ThemableBehavior,
- FakeRectangularElevationBehavior,
- ButtonBehavior,
- MDBoxLayout,
+ DeclarativeBehavior, ThemableBehavior, ButtonBehavior, BoxLayout
):
text = StringProperty()
"""
diff --git a/sbapp/kivymd/uix/filemanager/filemanager.kv b/sbapp/kivymd/uix/filemanager/filemanager.kv
index 57ab89b7..ad5704f4 100644
--- a/sbapp/kivymd/uix/filemanager/filemanager.kv
+++ b/sbapp/kivymd/uix/filemanager/filemanager.kv
@@ -6,28 +6,29 @@
background_normal: ""
background_down: ""
dir_or_file_name: ""
+ icon_color: 0, 0, 0, 0
_selected: False
events_callback: lambda x: None
orientation: "vertical"
ModifiedOneLineIconListItem:
text: root.dir_or_file_name
+ on_release: root.events_callback(root.path, root)
bg_color:
self.theme_cls.bg_darkest \
- if root._selected else self.theme_cls.bg_normal
- on_release: root.events_callback(root.path, root)
+ if root._selected else \
+ self.theme_cls.bg_normal
IconLeftWidget:
icon: root.icon
- theme_text_color: "Custom"
- text_color: self.theme_cls.primary_color
+ theme_icon_color: "Custom"
+ icon_color: root.icon_color
MDSeparator:
<LabelContent@MDLabel>
- size_hint_y: None
- height: self.texture_size[1]
+ adaptive_height: True
shorten: True
shorten_from: "center"
halign: "center"
@@ -61,23 +62,6 @@
text: root.name
-<FloatButton>
- anchor_x: "right"
- anchor_y: "bottom"
- size_hint_y: None
- height: dp(56)
- padding: dp(10)
-
- MDFloatingActionButton:
- size_hint: None, None
- size:dp(56), dp(56)
- icon: root.icon
- opposite_colors: True
- elevation: 8
- on_release: root.callback()
- md_bg_color: root.md_bg_color
-
-
<MDFileManager>
md_bg_color: root.theme_cls.bg_normal
@@ -90,7 +74,11 @@
title: root.current_path
right_action_items: [["close-box", lambda x: root.exit_manager(1)]]
left_action_items: [["chevron-left", lambda x: root.back()]]
- elevation: 10
+ elevation: 3
+ md_bg_color:
+ app.theme_cls.primary_color \
+ if not root.background_color_toolbar else \
+ root.background_color_toolbar
RecycleView:
id: rv
diff --git a/sbapp/kivymd/uix/filemanager/filemanager.py b/sbapp/kivymd/uix/filemanager/filemanager.py
index 3386c034..1b7727ef 100755
--- a/sbapp/kivymd/uix/filemanager/filemanager.py
+++ b/sbapp/kivymd/uix/filemanager/filemanager.py
@@ -9,7 +9,7 @@ Usage
.. code-block:: python
- path = '/' # path to the directory that will be opened in the file manager
+ path = os.path.expanduser("~") # path to the directory that will be opened in the file manager
file_manager = MDFileManager(
exit_manager=self.exit_manager, # function called when the user reaches directory tree root
select_path=self.select_path, # function called when selecting a file/directory
@@ -19,7 +19,7 @@ Usage
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/file-manager.png
:align: center
-.. warning:: Be careful! To use the `/` path on Android devices, you need
+.. warning:: Be careful! To use the `'/'` path on Android devices, you need
special permissions. Therefore, you are likely to get an error.
Or with ``preview`` mode:
@@ -32,7 +32,7 @@ Or with ``preview`` mode:
preview=True,
)
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/file-manager-previous.png
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/file-manager-preview.png
:align: center
.. warning:: The `preview` mode is intended only for viewing images and will
@@ -43,6 +43,8 @@ Example
.. code-block:: python
+ import os
+
from kivy.core.window import Window
from kivy.lang import Builder
@@ -53,19 +55,19 @@ Example
KV = '''
MDBoxLayout:
- orientation: 'vertical'
+ orientation: "vertical"
MDTopAppBar:
title: "MDFileManager"
- left_action_items: [['menu', lambda x: None]]
- elevation: 10
+ left_action_items: [["menu", lambda x: None]]
+ elevation: 3
MDFloatLayout:
MDRoundFlatIconButton:
text: "Open manager"
icon: "folder"
- pos_hint: {'center_x': .5, 'center_y': .6}
+ pos_hint: {"center_x": .5, "center_y": .5}
on_release: app.file_manager_open()
'''
@@ -76,23 +78,23 @@ Example
Window.bind(on_keyboard=self.events)
self.manager_open = False
self.file_manager = MDFileManager(
- exit_manager=self.exit_manager,
- select_path=self.select_path,
- preview=True,
+ exit_manager=self.exit_manager, select_path=self.select_path
)
def build(self):
+ self.theme_cls.theme_style = "Dark"
+ self.theme_cls.primary_palette = "Orange"
return Builder.load_string(KV)
def file_manager_open(self):
- self.file_manager.show('/') # output manager to the screen
+ self.file_manager.show(os.path.expanduser("~")) # output manager to the screen
self.manager_open = True
- def select_path(self, path):
- '''It will be called when you click on the file name
+ def select_path(self, path: str):
+ '''
+ It will be called when you click on the file name
or the catalog selection button.
- :type path: str;
:param path: path to the selected directory or file;
'''
@@ -126,6 +128,9 @@ Not tested on `iOS`.
def file_manager_open(self):
self.file_manager.show_disks()
+
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/file-manager-show-disks.png
+ :align: center
"""
__all__ = ("MDFileManager",)
@@ -136,6 +141,7 @@ import re
from typing import List, Tuple, Union
from kivy import platform
+from kivy.clock import Clock
from kivy.factory import Factory
from kivy.lang import Builder
from kivy.metrics import dp
@@ -148,7 +154,6 @@ from kivy.properties import (
OptionProperty,
StringProperty,
)
-from kivy.uix.anchorlayout import AnchorLayout
from kivy.uix.behaviors import ButtonBehavior
from kivy.uix.modalview import ModalView
@@ -156,6 +161,7 @@ from kivymd import images_path, uix_path
from kivymd.theming import ThemableBehavior
from kivymd.uix.behaviors import CircularRippleBehavior
from kivymd.uix.boxlayout import MDBoxLayout
+from kivymd.uix.button import MDFloatingActionButton
from kivymd.uix.fitimage import FitImage
from kivymd.uix.list import BaseListItem
from kivymd.uix.relativelayout import MDRelativeLayout
@@ -167,9 +173,7 @@ with open(
class BodyManager(MDBoxLayout):
- """
- Base class for folders and files icons.
- """
+ """Base class for folders and files icons."""
class BodyManagerWithPreview(MDBoxLayout):
@@ -182,47 +186,146 @@ class IconButton(CircularRippleBehavior, ButtonBehavior, FitImage):
"""Folder icons/thumbnails images in ``preview`` mode."""
-class FloatButton(ThemableBehavior, AnchorLayout):
- callback = ObjectProperty()
- md_bg_color = ColorProperty([1, 1, 1, 1])
- icon = StringProperty()
-
- def __init__(self, **kwargs):
- super().__init__(**kwargs)
- self.theme_cls.bind(primary_palette=self.set_md_bg_color)
-
- def set_md_bg_color(self, *args):
- self.md_bg_color = self.theme_cls.primary_color
-
-
class ModifiedOneLineIconListItem(BaseListItem):
_txt_left_pad = NumericProperty("72dp")
_txt_top_pad = NumericProperty("16dp")
_txt_bot_pad = NumericProperty("15dp")
_num_lines = 1
- def __init__(self, **kwargs):
- super().__init__(**kwargs)
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
self.height = dp(48)
-class MDFileManager(ThemableBehavior, MDRelativeLayout):
- icon = StringProperty("check")
+class MDFileManager(MDRelativeLayout, ThemableBehavior):
+ """
+ Implements a modal dialog with a file manager.
+
+ For more information, see in the
+ :class:`~kivymd.uix.relativelayout.MDRelativeLayout` class documentation.
+
+ :Events:
+ `on_pre_open`:
+ Called before the MDFileManager is opened.
+ `on_open`:
+ Called when the MDFileManager is opened.
+ `on_pre_dismiss`:
+ Called before the MDFileManager is closed.
+ `on_dismiss`:
+ Called when the MDFileManager is closed.
+ """
+
+ icon = StringProperty("check", deprecated=True)
"""
- The icon that will be used on the directory selection button.
+ Icon that will be used on the directory selection button.
+
+ .. deprecated:: 1.1.0
+ Use :attr:`icon_selection_button` instead.
:attr:`icon` is an :class:`~kivy.properties.StringProperty`
and defaults to `check`.
"""
+ icon_selection_button = StringProperty("check")
+ """
+ Icon that will be used on the directory selection button.
+
+ .. versionadded:: 1.1.0
+
+ .. code-block:: python
+
+ MDFileManager(
+ ...
+ icon_selection_button="pencil",
+ )
+
+ .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/file-manager-icon-selection-button.png
+ :align: center
+
+ :attr:`icon_selection_button` is an :class:`~kivy.properties.StringProperty`
+ and defaults to `check`.
+ """
+
+ background_color_selection_button = ColorProperty(None)
+ """
+ Background color of the current directory/path selection button.
+
+ .. versionadded:: 1.1.0
+
+ .. code-block:: python
+
+ MDFileManager(
+ ...
+ background_color_selection_button="brown",
+ )
+
+ .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/file-manager-background-color-selection-button.png
+ :align: center
+
+ :attr:`background_color_selection_button` is an :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
+
+ background_color_toolbar = ColorProperty(None)
+ """
+ Background color of the file manager toolbar.
+
+ .. versionadded:: 1.1.0
+
+ .. code-block:: python
+
+ MDFileManager(
+ ...
+ background_color_toolbar="brown",
+ )
+
+ .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/file-manager-background-color-toolbar.png
+ :align: center
+
+ :attr:`background_color_toolbar` is an :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
+
icon_folder = StringProperty(f"{images_path}folder.png")
"""
- The icon that will be used for folder icons when using ``preview = True``.
+ Icon that will be used for folder icons when using ``preview = True``.
+
+ .. code-block:: python
+
+ MDFileManager(
+ ...
+ preview=True,
+ icon_folder="path/to/icon.png",
+ )
+
+ .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/file-manager-icon-folder.png
+ :align: center
:attr:`icon` is an :class:`~kivy.properties.StringProperty`
and defaults to `check`.
"""
+ icon_color = ColorProperty(None)
+ """
+ Color of the folder icon when the :attr:`preview` property is set to False.
+
+ .. versionadded:: 1.1.0
+
+ .. code-block:: python
+
+ MDFileManager(
+ ...
+ preview=False,
+ icon_color="brown",
+ )
+
+ .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/file-manager-icon-color.png
+ :align: center
+
+ :attr:`icon_color` is an :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
+
exit_manager = ObjectProperty(lambda x: None)
"""
Function called when the user reaches directory tree root.
@@ -259,12 +362,12 @@ class MDFileManager(ThemableBehavior, MDRelativeLayout):
and defaults to `all`.
"""
- current_path = StringProperty(os.getcwd())
+ current_path = StringProperty(os.path.expanduser("~"))
"""
Current directory.
:attr:`current_path` is an :class:`~kivy.properties.StringProperty`
- and defaults to `/`.
+ and defaults to `os.path.expanduser("~")`.
"""
use_access = BooleanProperty(True)
@@ -295,9 +398,9 @@ class MDFileManager(ThemableBehavior, MDRelativeLayout):
"name", options=["nothing", "name", "date", "size", "type"]
)
"""
- It can take the values 'nothing' 'name' 'date' 'size' 'type' - sorts files by option
- By default, sort by name.
- Available options are: `'nothing'`, `'name'`, `'date'`, `'size'`, `'type'`.
+ It can take the values 'nothing' 'name' 'date' 'size' 'type' - sorts files
+ by option. By default, sort by name. Available options are:
+ `'nothing'`, `'name'`, `'date'`, `'size'`, `'type'`.
:attr:`sort_by` is an :class:`~kivy.properties.OptionProperty`
and defaults to `name`.
@@ -325,29 +428,33 @@ class MDFileManager(ThemableBehavior, MDRelativeLayout):
"""
Contains the list of files that are currently selected.
- :attr:`selection` is a read-only :class:`~kivy.properties.ListProperty` and
- defaults to `[]`.
+ :attr:`selection` is a read-only :class:`~kivy.properties.ListProperty`
+ and defaults to `[]`.
+ """
+
+ selection_button = ObjectProperty()
+ """
+ The instance of the directory/path selection button.
+
+ .. versionadded:: 1.1.0
+
+ :attr:`selection_button` is a read-only :class:`~kivy.properties.ObjectProperty`
+ and defaults to `None`.
"""
_window_manager = None
_window_manager_open = False
- def __init__(self, **kwargs):
- super().__init__(**kwargs)
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.register_event_type("on_pre_open")
+ self.register_event_type("on_open")
+ self.register_event_type("on_pre_dismiss")
+ self.register_event_type("on_dismiss")
+
toolbar_label = self.ids.toolbar.children[1].children[0]
toolbar_label.font_style = "Subtitle1"
- if (
- self.selector == "any"
- or self.selector == "multi"
- or self.selector == "folder"
- ):
- self.add_widget(
- FloatButton(
- callback=self.select_directory_on_press_button,
- md_bg_color=self.theme_cls.primary_color,
- icon=self.icon,
- )
- )
+ Clock.schedule_once(self._create_selection_button)
if self.preview:
self.ext = [".png", ".jpg", ".jpeg"]
@@ -400,15 +507,7 @@ class MDFileManager(ThemableBehavior, MDRelativeLayout):
}
)
self.ids.rv.data = manager_list
-
- if not self._window_manager:
- self._window_manager = ModalView(
- size_hint=self.size_hint, auto_dismiss=False
- )
- self._window_manager.add_widget(self)
- if not self._window_manager_open:
- self._window_manager.open()
- self._window_manager_open = True
+ self._show()
def show(self, path: str) -> None:
"""
@@ -474,6 +573,9 @@ class MDFileManager(ThemableBehavior, MDRelativeLayout):
"icon": icon,
"dir_or_file_name": name,
"events_callback": self.select_dir_or_file,
+ "icon_color": self.theme_cls.primary_color
+ if not self.icon_color
+ else self.icon_color,
"_selected": False,
}
)
@@ -488,19 +590,14 @@ class MDFileManager(ThemableBehavior, MDRelativeLayout):
"icon": "file-outline",
"dir_or_file_name": os.path.split(name)[1],
"events_callback": self.select_dir_or_file,
+ "icon_color": self.theme_cls.primary_color
+ if not self.icon_color
+ else self.icon_color,
"_selected": False,
}
)
self.ids.rv.data = manager_list
-
- if not self._window_manager:
- self._window_manager = ModalView(
- size_hint=self.size_hint, auto_dismiss=False
- )
- self._window_manager.add_widget(self)
- if not self._window_manager_open:
- self._window_manager.open()
- self._window_manager_open = True
+ self._show()
def get_access_string(self, path: str) -> str:
access_string = ""
@@ -557,7 +654,9 @@ class MDFileManager(ThemableBehavior, MDRelativeLayout):
def close(self) -> None:
"""Closes the file manager window."""
+ self.dispatch("on_pre_dismiss")
self._window_manager.dismiss()
+ self.dispatch("on_dismiss")
self._window_manager_open = False
def select_dir_or_file(
@@ -609,6 +708,84 @@ class MDFileManager(ThemableBehavior, MDRelativeLayout):
if self.selector == "folder" or self.selector == "any":
self.select_path(self.current_path)
+ def on_icon(self, instance_file_manager, icon_name: str) -> None:
+ """Called when the :attr:`icon` property is changed."""
+
+ self.icon_selection_button = icon_name
+
+ def on_background_color_toolbar(
+ self, instance_file_manager, color: Union[str, list]
+ ) -> None:
+ """
+ Called when the :attr:`background_color_toolbar` property is changed.
+ """
+
+ def on_background_color_toolbar(*args):
+ self.ids.toolbar.md_bg_color = color
+
+ Clock.schedule_once(on_background_color_toolbar)
+
+ def on_pre_open(self, *args):
+ """
+ Default pre-open event handler.
+
+ .. versionadded:: 1.1.0
+ """
+
+ def on_open(self, *args):
+ """
+ Default open event handler.
+
+ .. versionadded:: 1.1.0
+ """
+
+ def on_pre_dismiss(self, *args):
+ """
+ Default pre-dismiss event handler.
+
+ .. versionadded:: 1.1.0
+ """
+
+ def on_dismiss(self, *args):
+ """
+ Default dismiss event handler.
+
+ .. versionadded:: 1.1.0
+ """
+
+ def _show(self):
+ if not self._window_manager:
+ self._window_manager = ModalView(
+ size_hint=self.size_hint, auto_dismiss=False
+ )
+ self.size_hint = (1, 1)
+ self._window_manager.add_widget(self)
+
+ if not self._window_manager_open:
+ self._window_manager.open()
+ self._window_manager_open = True
+
+ self.dispatch("on_pre_open")
+ self.dispatch("on_open")
+
+ def _create_selection_button(self, *args):
+ if (
+ self.selector == "any"
+ or self.selector == "multi"
+ or self.selector == "folder"
+ ):
+ self.selection_button = MDFloatingActionButton(
+ on_release=self.select_directory_on_press_button,
+ md_bg_color=self.theme_cls.primary_color
+ if not self.background_color_selection_button
+ else self.background_color_selection_button,
+ icon=self.icon_selection_button,
+ pos_hint={"right": 0.99},
+ y=dp(12),
+ elevation=0,
+ )
+ self.add_widget(self.selection_button)
+
def __sort_files(self, files):
def sort_by_name(files):
files.sort(key=locale.strxfrm)
diff --git a/sbapp/kivymd/uix/fitimage/fitimage.py b/sbapp/kivymd/uix/fitimage/fitimage.py
index c67cec22..35ded4d9 100644
--- a/sbapp/kivymd/uix/fitimage/fitimage.py
+++ b/sbapp/kivymd/uix/fitimage/fitimage.py
@@ -132,11 +132,11 @@ from kivy.properties import BooleanProperty, ObjectProperty
from kivy.uix.image import AsyncImage
from kivy.uix.widget import Widget
+from kivymd.uix.behaviors import StencilBehavior
from kivymd.uix.boxlayout import MDBoxLayout
-from kivymd.uix.templates import StencilWidget
-class FitImage(MDBoxLayout, StencilWidget):
+class FitImage(MDBoxLayout, StencilBehavior):
source = ObjectProperty()
"""
Filename/source of your image.
diff --git a/sbapp/kivymd/uix/gridlayout.py b/sbapp/kivymd/uix/gridlayout.py
index 7c296b3c..84d883cf 100644
--- a/sbapp/kivymd/uix/gridlayout.py
+++ b/sbapp/kivymd/uix/gridlayout.py
@@ -90,4 +90,7 @@ from kivymd.uix.behaviors import DeclarativeBehavior
class MDGridLayout(DeclarativeBehavior, GridLayout, MDAdaptiveWidget):
- pass
+ """
+ Grid layout class. For more information, see in the
+ :class:`~kivy.uix.gridlayout.GridLayout` class documentation.
+ """
diff --git a/sbapp/kivymd/uix/hero.py b/sbapp/kivymd/uix/hero.py
index 34eef37e..70bb572b 100644
--- a/sbapp/kivymd/uix/hero.py
+++ b/sbapp/kivymd/uix/hero.py
@@ -63,7 +63,7 @@ Base example
x: 24
FitImage:
- source: "https://github.com/kivymd/internal/raw/main/logo/kivymd_logo_blue.png"
+ source: "kivymd/images/logo/kivymd-icon-512.png"
size_hint: None, None
size: hero_from.size
@@ -72,7 +72,7 @@ Base example
pos_hint: {"center_x": .5}
y: "36dp"
on_release:
- root.current_hero = "hero"
+ root.current_heroes = ["hero"]
root.current = "screen B"
MDScreen:
@@ -82,6 +82,7 @@ Base example
MDHeroTo:
id: hero_to
+ tag: "hero"
size_hint: None, None
size: "220dp", "220dp"
pos_hint: {"center_x": .5, "center_y": .5}
@@ -91,7 +92,7 @@ Base example
pos_hint: {"center_x": .5}
y: "36dp"
on_release:
- root.current_hero = "hero"
+ root.current_heroes = ["hero"]
root.current = "screen A"
'''
@@ -113,6 +114,7 @@ Note that the child of the :class:`~MDHeroFrom` widget must have the size of the
MDHeroFrom:
id: hero_from
+ tag: "hero"
FitImage:
size_hint: None, None
@@ -127,7 +129,7 @@ container in which the hero is located:
MDRaisedButton:
text: "Move Hero To Screen B"
on_release:
- root.current_hero = "hero"
+ root.current_heroes = ["hero"]
root.current = "screen 2"
If you need to switch to a screen that does not contain heroes, set the
@@ -138,7 +140,7 @@ If you need to switch to a screen that does not contain heroes, set the
MDRaisedButton:
text: "Go To Another Screen"
on_release:
- root.current_hero = ""
+ root.current_heroes = []
root.current = "another screen"
Example
@@ -166,7 +168,7 @@ Example
x: 24
FitImage:
- source: "https://github.com/kivymd/internal/raw/main/logo/kivymd_logo_blue.png"
+ source: "kivymd/images/logo/kivymd-icon-512.png"
size_hint: None, None
size: hero_from.size
@@ -175,7 +177,7 @@ Example
pos_hint: {"center_x": .5}
y: "36dp"
on_release:
- root.current_hero = "hero"
+ root.current_heroes = ["hero"]
root.current = "screen B"
MDScreen:
@@ -185,6 +187,7 @@ Example
MDHeroTo:
id: hero_to
+ tag: "hero"
size_hint: None, None
size: "220dp", "220dp"
pos_hint: {"center_x": .5, "center_y": .5}
@@ -194,7 +197,7 @@ Example
pos_hint: {"center_x": .5}
y: "52dp"
on_release:
- root.current_hero = ""
+ root.current_heroes = []
root.current = "screen C"
MDRaisedButton:
@@ -202,7 +205,7 @@ Example
pos_hint: {"center_x": .5}
y: "8dp"
on_release:
- root.current_hero = "hero"
+ root.current_heroes = ["hero"]
root.current = "screen A"
MDScreen:
@@ -283,7 +286,7 @@ background color of the hero during the flight between the screens:
pos_hint: {"center_x": .5}
y: "36dp"
on_release:
- root.current_hero = "hero"
+ root.current_heroes = ["hero"]
root.current = "screen B"
MDScreen:
@@ -293,6 +296,7 @@ background color of the hero during the flight between the screens:
MDHeroTo:
id: hero_to
+ tag: "hero"
size_hint: None, None
size: "220dp", "220dp"
pos_hint: {"center_x": .5, "center_y": .5}
@@ -302,7 +306,7 @@ background color of the hero during the flight between the screens:
pos_hint: {"center_x": .5}
y: "36dp"
on_release:
- root.current_hero = "hero"
+ root.current_heroes = ["hero"]
root.current = "screen A"
'''
@@ -370,7 +374,7 @@ Usage with ScrollView
radius: 24
box_radius: 0, 0, 24, 24
box_color: 0, 0, 0, .5
- source: "image.jpg"
+ source: "kivymd/images/logo/kivymd-icon-512.png"
size_hint: None, None
size: root.size
mipmap: True
@@ -399,7 +403,7 @@ Usage with ScrollView
MDScreen:
name: "screen B"
- hero_to: hero_to
+ heroes_to: [hero_to]
MDHeroTo:
id: hero_to
@@ -412,7 +416,7 @@ Usage with ScrollView
pos_hint: {"center_x": .5}
y: "36dp"
on_release:
- root.current_hero = "hero"
+ root.current_heroes = [hero_to.tag]
root.current = "screen A"
'''
@@ -441,7 +445,8 @@ Usage with ScrollView
def on_release(self):
def switch_screen(*args):
- self.manager.current_hero = self.tag
+ self.manager.current_heroes = [self.tag]
+ self.manager.ids.hero_to.tag = self.tag
self.manager.current = "screen B"
Clock.schedule_once(switch_screen, 0.2)
@@ -465,6 +470,93 @@ Usage with ScrollView
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/hero-usage-with-scrollview.gif
:align: center
+
+Using multiple heroes at the same time
+--------------------------------------
+
+.. code-block:: python
+
+ from kivy.lang import Builder
+
+ from kivymd.app import MDApp
+
+ KV = '''
+ MDScreenManager:
+
+ MDScreen:
+ name: "screen A"
+ md_bg_color: "lightblue"
+
+ MDHeroFrom:
+ id: hero_kivymd
+ tag: "kivymd"
+ size_hint: None, None
+ size: "200dp", "200dp"
+ pos_hint: {"top": .98}
+ x: 24
+
+ FitImage:
+ source: "kivymd/images/logo/kivymd-icon-512.png"
+ size_hint: None, None
+ size: hero_kivymd.size
+
+ MDHeroFrom:
+ id: hero_kivy
+ tag: "kivy"
+ size_hint: None, None
+ size: "200dp", "200dp"
+ pos_hint: {"top": .98}
+ x: 324
+
+ FitImage:
+ source: "data/logo/kivy-icon-512.png"
+ size_hint: None, None
+ size: hero_kivy.size
+
+ MDRaisedButton:
+ text: "Move Hero To Screen B"
+ pos_hint: {"center_x": .5}
+ y: "36dp"
+ on_release:
+ root.current_heroes = ["kivymd", "kivy"]
+ root.current = "screen B"
+
+ MDScreen:
+ name: "screen B"
+ heroes_to: hero_to_kivymd, hero_to_kivy
+ md_bg_color: "cadetblue"
+
+ MDHeroTo:
+ id: hero_to_kivy
+ tag: "kivy"
+ size_hint: None, None
+ pos_hint: {"center_x": .5, "center_y": .5}
+
+ MDHeroTo:
+ id: hero_to_kivymd
+ tag: "kivymd"
+ size_hint: None, None
+ pos_hint: {"right": 1, "top": 1}
+
+ MDRaisedButton:
+ text: "Move Hero To Screen A"
+ pos_hint: {"center_x": .5}
+ y: "36dp"
+ on_release:
+ root.current_heroes = ["kivy", "kivymd"]
+ root.current = "screen A"
+ '''
+
+
+ class Test(MDApp):
+ def build(self):
+ return Builder.load_string(KV)
+
+
+ Test().run()
+
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/hero-multiple-heroes.gif
+ :align: center
"""
from kivy.properties import StringProperty
@@ -476,6 +568,9 @@ class MDHeroFrom(MDBoxLayout):
"""
The container from which the hero begins his flight.
+ For more information, see in the
+ :class:`~kivymd.uix.boxlayout.MDBoxLayout` class documentation.
+
:Events:
`on_transform_in`
when the hero flies from screen **A** to screen **B**.
@@ -487,7 +582,7 @@ class MDHeroFrom(MDBoxLayout):
"""
Tag ID for heroes.
- :attr:`shift_right` is an :class:`~kivy.properties.StringProperty`
+ :attr:`tag` is an :class:`~kivy.properties.StringProperty`
and defaults to `''`.
"""
@@ -504,4 +599,17 @@ class MDHeroFrom(MDBoxLayout):
class MDHeroTo(MDBoxLayout):
- """The container in which the hero comes."""
+ """
+ The container in which the hero comes.
+
+ For more information, see in the
+ :class:`~kivymd.uix.boxlayout.MDBoxLayout` class documentation.
+ """
+
+ tag = StringProperty(allownone=True)
+ """
+ Tag ID for heroes.
+
+ :attr:`tag` is an :class:`~kivy.properties.StringProperty`
+ and defaults to `''`.
+ """
diff --git a/sbapp/kivymd/uix/imagelist/imagelist.py b/sbapp/kivymd/uix/imagelist/imagelist.py
index 310cee36..970ed2e5 100755
--- a/sbapp/kivymd/uix/imagelist/imagelist.py
+++ b/sbapp/kivymd/uix/imagelist/imagelist.py
@@ -65,12 +65,13 @@ Implementation
:align: center
"""
-__all__ = "MDSmartTile"
+__all__ = [
+ "MDSmartTile",
+]
import os
from kivy.lang import Builder
-from kivy.logger import Logger
from kivy.properties import (
BooleanProperty,
ColorProperty,
diff --git a/sbapp/kivymd/uix/label/label.kv b/sbapp/kivymd/uix/label/label.kv
index b9743361..71cac919 100644
--- a/sbapp/kivymd/uix/label/label.kv
+++ b/sbapp/kivymd/uix/label/label.kv
@@ -17,8 +17,14 @@
rgba: (1, 1, 1, 1) if self.source else (0, 0, 0, 0)
Rectangle:
source: self.source if self.source else None
- pos: self.pos
- size: self.size
+ pos:
+ self.pos \
+ if not self.source else \
+ (self.x - self._size[0] / 2, self.y)
+ size:
+ self._size \
+ if self.source else \
+ self.size
font_style: "Icon"
text: u"{}".format(md_icons[root.icon]) if root.icon in md_icons else "blank"
diff --git a/sbapp/kivymd/uix/label/label.py b/sbapp/kivymd/uix/label/label.py
index 9d2a65f2..fc34ba4f 100755
--- a/sbapp/kivymd/uix/label/label.py
+++ b/sbapp/kivymd/uix/label/label.py
@@ -222,14 +222,18 @@ __all__ = ("MDLabel", "MDIcon")
import os
from typing import Union
+from kivy.animation import Animation
from kivy.clock import Clock
+from kivy.graphics import Color, Rectangle
from kivy.lang import Builder
from kivy.metrics import sp
from kivy.properties import (
AliasProperty,
BooleanProperty,
ColorProperty,
+ ListProperty,
NumericProperty,
+ ObjectProperty,
OptionProperty,
StringProperty,
)
@@ -322,6 +326,7 @@ class MDLabel(DeclarativeBehavior, ThemableBehavior, Label, MDAdaptiveWidget):
parent_background = ColorProperty(None)
can_capitalize = BooleanProperty(True)
+ canvas_bg = ObjectProperty()
def __init__(self, **kwargs):
super().__init__(**kwargs)
@@ -349,6 +354,7 @@ class MDLabel(DeclarativeBehavior, ThemableBehavior, Label, MDAdaptiveWidget):
font_info = self.theme_cls.font_styles[self.font_style]
self.font_name = font_info[0]
self.font_size = sp(font_info[1])
+
if font_info[2] and self.can_capitalize:
self._capitalizing = True
else:
@@ -374,29 +380,68 @@ class MDLabel(DeclarativeBehavior, ThemableBehavior, Label, MDAdaptiveWidget):
# generic None value it's not yet been set
self._text_color_str = ""
if theme_text_color == "Custom" and self.text_color:
- self.color = self.text_color
+ color = self.text_color
elif (
theme_text_color == "ContrastParentBackground"
and self.parent_background
):
- self.color = get_contrast_text_color(self.parent_background)
+ color = get_contrast_text_color(self.parent_background)
else:
- self.color = [0, 0, 0, 1]
+ color = [0, 0, 0, 1]
+
+ if self.theme_cls.theme_style_switch_animation:
+ Animation(
+ color=color,
+ d=self.theme_cls.theme_style_switch_animation_duration,
+ t="linear",
+ ).start(self)
+ else:
+ self.color = color
- def on_text_color(self, instance_label, color: list) -> None:
+ def on_text_color(self, instance_label, color: Union[list, str]) -> None:
if self.theme_text_color == "Custom":
- self.color = self.text_color
+ if self.theme_cls.theme_style_switch_animation:
+ Animation(
+ color=self.text_color,
+ d=self.theme_cls.theme_style_switch_animation_duration,
+ t="linear",
+ ).start(self)
+ else:
+ self.color = self.text_color
def on_opposite_colors(self, *args) -> None:
self.on_theme_text_color(self, self.theme_text_color)
+ def on_md_bg_color(self, instance_label, color: Union[list, str]) -> None:
+ self.canvas.remove_group("Background_instruction")
+ with self.canvas.before:
+ Color(rgba=color)
+ self.canvas_bg = Rectangle(pos=self.pos, size=self.size)
+ self.bind(pos=self.update_canvas_bg_pos)
+
+ def on_size(self, instance_label, size: list) -> None:
+ if self.canvas_bg:
+ self.canvas_bg.size = size
+
+ def update_canvas_bg_pos(self, instance_label, pos: list) -> None:
+ if self.canvas_bg:
+ self.canvas_bg.pos = pos
+
def _do_update_theme_color(self, *args):
if self._text_color_str:
- self.color = getattr(self.theme_cls, self._text_color_str)
if not self.disabled:
- self.color = getattr(self.theme_cls, self._text_color_str)
+ color = getattr(self.theme_cls, self._text_color_str)
+ else:
+ color = getattr(self.theme_cls, "disabled_hint_text_color")
+
+ if self.theme_cls.theme_style_switch_animation:
+ Animation(
+ color=color,
+ d=self.theme_cls.theme_style_switch_animation_duration,
+ t="linear",
+ ).start(self)
else:
- self.color = getattr(self.theme_cls, "disabled_hint_text_color")
+ self.color = color
class MDIcon(MDFloatLayout, MDLabel):
@@ -456,11 +501,16 @@ class MDIcon(MDFloatLayout, MDLabel):
and defaults to `None`.
"""
- def __init__(self, **kwargs):
+ _size = ListProperty((0, 0))
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ Clock.schedule_once(self.adjust_size)
+
+ def adjust_size(self, *args) -> None:
from kivymd.uix.selectioncontrol import MDCheckbox
- super().__init__(**kwargs)
if not isinstance(self, MDCheckbox):
self.size_hint = (None, None)
- self.size = self.texture_size
+ self._size = self.texture_size[1], self.texture_size[1]
self.adaptive_size = True
diff --git a/sbapp/kivymd/uix/list/list.py b/sbapp/kivymd/uix/list/list.py
index 5f4443f1..9f02cabd 100755
--- a/sbapp/kivymd/uix/list/list.py
+++ b/sbapp/kivymd/uix/list/list.py
@@ -57,6 +57,7 @@ based on the above classes.
- OneLineAvatarListItem_
- TwoLineAvatarListItem_
- ThreeLineAvatarListItem_
+
- OneLineIconListItem_
- TwoLineIconListItem_
- ThreeLineIconListItem_
@@ -68,35 +69,71 @@ based on the above classes.
- TwoLineAvatarIconListItem_
- ThreeLineAvatarIconListItem_
+- OneLineRightIconListItem_
+- TwoLineRightIconListItem_
+- ThreeLineRightIconListItem_
+
Usage
-----
-.. code-block:: python
+.. tabs::
+
+ .. tab:: Declarative KV style
+
+ .. code-block:: python
+
+ from kivy.lang import Builder
+
+ from kivymd.app import MDApp
+ from kivymd.uix.list import OneLineListItem
+
+ KV = '''
+ MDScrollView:
+
+ MDList:
+ id: container
+ '''
+
+
+ class Example(MDApp):
+ def build(self):
+ self.theme_cls.theme_style = "Dark"
+ return Builder.load_string(KV)
+
+ def on_start(self):
+ for i in range(20):
+ self.root.ids.container.add_widget(
+ OneLineListItem(text=f"Single-line item {i}")
+ )
- from kivy.lang import Builder
+ Example().run()
- from kivymd.app import MDApp
- from kivymd.uix.list import OneLineListItem
+ .. tab:: Declarative python style
- KV = '''
- ScrollView:
+ .. code-block:: python
- MDList:
- id: container
- '''
+ from kivymd.app import MDApp
+ from kivymd.uix.list import OneLineListItem
- class Test(MDApp):
- def build(self):
- return Builder.load_string(KV)
+ class Example(MDApp):
+ def build(self):
+ self.theme_cls.theme_style = "Dark"
+ return (
+ MDScrollView(
+ MDList(
+ id="container"
+ )
+ )
+ )
- def on_start(self):
- for i in range(20):
- self.root.ids.container.add_widget(
- OneLineListItem(text=f"Single-line item {i}")
- )
+ def on_start(self):
+ for i in range(20):
+ self.root.ids.container.add_widget(
+ OneLineListItem(text=f"Single-line item {i}")
+ )
- Test().run()
+ Example().run()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/lists.gif
:align: center
@@ -104,37 +141,76 @@ Usage
Events of List
--------------
-.. code-block:: python
+.. tabs::
- from kivy.lang import Builder
+ .. tab:: Declarative KV style
- from kivymd.app import MDApp
+ .. code-block:: python
- KV = '''
- ScrollView:
+ from kivy.lang import Builder
- MDList:
+ from kivymd.app import MDApp
- OneLineAvatarIconListItem:
- on_release: print("Click!")
+ KV = '''
+ MDScrollView:
- IconLeftWidget:
- icon: "github"
+ MDList:
- OneLineAvatarIconListItem:
- on_release: print("Click 2!")
+ OneLineAvatarIconListItem:
+ on_release: print("Click!")
- IconLeftWidget:
- icon: "gitlab"
- '''
+ IconLeftWidget:
+ icon: "github"
+
+ OneLineAvatarIconListItem:
+ on_release: print("Click 2!")
+
+ IconLeftWidget:
+ icon: "gitlab"
+ '''
+
+
+ class Example(MDApp):
+ def build(self):
+ self.theme_cls.theme_style = "Dark"
+ return Builder.load_string(KV)
+
+
+ Example().run()
+
+ .. tab:: Declarative python style
+ .. code-block:: python
- class MainApp(MDApp):
- def build(self):
- return Builder.load_string(KV)
+ from kivymd.app import MDApp
+ from kivymd.uix.scrollview import MDScrollView
+ from kivymd.uix.list import MDList, OneLineAvatarIconListItem, IconLeftWidget
- MainApp().run()
+ class Example(MDApp):
+ def build(self):
+ self.theme_cls.theme_style = "Dark"
+ return (
+ MDScrollView(
+ MDList(
+ OneLineAvatarIconListItem(
+ IconLeftWidget(
+ icon="github"
+ ),
+ on_release=lambda x: print("Click!")
+ ),
+ OneLineAvatarIconListItem(
+ IconLeftWidget(
+ icon="gitlab"
+ ),
+ on_release=lambda x: print("Click 2!")
+ ),
+ )
+ )
+ )
+
+
+ Example().run()
.. OneLineListItem:
OneLineListItem
@@ -179,62 +255,220 @@ ThreeLineListItem
OneLineAvatarListItem
---------------------
-.. code-block:: kv
+.. tabs::
+
+ .. tab:: Declarative KV style
+
+ .. code-block:: kv
+
+ OneLineAvatarListItem:
+ text: "Single-line item with avatar"
- OneLineAvatarListItem:
- text: "Single-line item with avatar"
+ ImageLeftWidget:
+ source: "kivymd/images/logo/kivymd-icon-256.png"
- ImageLeftWidget:
- source: "data/logo/kivy-icon-256.png"
+ .. tab:: Declarative python style
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/lists-map.png
+ .. code-block:: python
+
+ OneLineAvatarListItem(
+ ImageLeftWidget(
+ source="kivymd/images/logo/kivymd-icon-256.png"
+ ),
+ text="Single-line item with avatar",
+ )
+
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/OneLineAvatarListItem.png
:align: center
.. TwoLineAvatarListItem:
TwoLineAvatarListItem
---------------------
-.. code-block:: kv
+.. tabs::
- TwoLineAvatarListItem:
- text: "Two-line item with avatar"
- secondary_text: "Secondary text here"
+ .. tab:: Declarative KV style
+
+ .. code-block:: kv
- ImageLeftWidget:
- source: "data/logo/kivy-icon-256.png"
+ TwoLineAvatarListItem:
+ text: "Two-line item with avatar"
+ secondary_text: "Secondary text here"
+
+ ImageLeftWidget:
+ source: "kivymd/images/logo/kivymd-icon-256.png"
+
+ .. tab:: Declarative python style
+
+ .. code-block:: python
+
+ OneLineAvatarListItem(
+ ImageLeftWidget(
+ source="kivymd/images/logo/kivymd-icon-256.png"
+ ),
+ text="Single-line item with avatar",
+ secondary_text: "Secondary text here",
+ )
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/TwoLineAvatarListItem.png
:align: center
-
.. ThreeLineAvatarListItem:
ThreeLineAvatarListItem
-----------------------
-.. code-block:: kv
+.. tabs::
- ThreeLineAvatarListItem:
- text: "Three-line item with avatar"
- secondary_text: "Secondary text here"
- tertiary_text: "fit more text than usual"
+ .. tab:: Declarative KV style
+
+ .. code-block:: kv
+
+ ThreeLineAvatarListItem:
+ text: "Three-line item with avatar"
+ secondary_text: "Secondary text here"
+ tertiary_text: "fit more text than usual"
- ImageLeftWidget:
- source: "data/logo/kivy-icon-256.png"
+ ImageLeftWidget:
+ source: "kivymd/images/logo/kivymd-icon-256.png"
+
+ .. tab:: Declarative python style
+
+ .. code-block:: python
+
+ OneLineAvatarListItem(
+ ImageLeftWidget(
+ source="kivymd/images/logo/kivymd-icon-256.png"
+ ),
+ text="Single-line item with avatar",
+ secondary_text: "Secondary text here",
+ tertiary_text: "fit more text than usual"
+ )
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/ThreeLineAvatarListItem.png
:align: center
+.. OneLineRightIconListItem:
+OneLineRightIconListItem
+------------------------
+
+.. tabs::
+
+ .. tab:: Declarative KV style
+
+ .. code-block:: kv
+
+ OneLineRightIconListItem:
+ text: "Single-line item with avatar"
+
+ ImageRightWidget:
+ source: "kivymd/images/logo/kivymd-icon-256.png"
+
+ .. tab:: Declarative python style
+
+ .. code-block:: python
+
+ OneLineRightIconListItem(
+ ImageRightWidget(
+ source="kivymd/images/logo/kivymd-icon-256.png"
+ ),
+ text="Single-line item with avatar",
+ )
+
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/OneLineRightIconListItem.png
+ :align: center
+
+.. TwoLineRightIconListItem:
+TwoLineRightIconListItem
+------------------------
+
+.. tabs::
+
+ .. tab:: Declarative KV style
+
+ .. code-block:: kv
+
+ TwoLineRightIconListItem:
+ text: "Single-line item with avatar"
+ secondary_text: "Secondary text here"
+
+ ImageRightWidget:
+ source: "kivymd/images/logo/kivymd-icon-256.png"
+
+ .. tab:: Declarative python style
+
+ .. code-block:: python
+
+ TwoLineRightIconListItem(
+ ImageRightWidget(
+ source="kivymd/images/logo/kivymd-icon-256.png"
+ ),
+ text="Single-line item with avatar",
+ secondary_text: "Secondary text here",
+ )
+
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/TwoLineRightIconListItem.png
+ :align: center
+
+.. ThreeLineRightIconListItem:
+ThreeLineRightIconListItem
+--------------------------
+
+.. tabs::
+
+ .. tab:: Declarative KV style
+
+ .. code-block:: kv
+
+ ThreeLineRightIconListItem:
+ text: "Single-line item with avatar"
+ secondary_text: "Secondary text here"
+ tertiary_text: "fit more text than usual"
+
+ ImageRightWidget:
+ source: "kivymd/images/logo/kivymd-icon-256.png"
+
+ .. tab:: Declarative python style
+
+ .. code-block:: python
+
+ ThreeLineRightIconListItem(
+ ImageRightWidget(
+ source="kivymd/images/logo/kivymd-icon-256.png"
+ ),
+ text="Single-line item with avatar",
+ secondary_text: "Secondary text here",
+ tertiary_text: "fit more text than usual",
+ )
+
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/ThreeLineRightIconListItem.png
+ :align: center
+
.. OneLineIconListItem:
OneLineIconListItem
-------------------
-.. code-block:: kv
+.. tabs::
+
+ .. tab:: Declarative KV style
+
+ .. code-block:: kv
+
+ OneLineIconListItem:
+ text: "Single-line item with avatar"
+
+ IconLeftWidget:
+ icon: "language-python"
+
+ .. tab:: Declarative python style
- OneLineIconListItem:
- text: "Single-line item with avatar"
+ .. code-block:: python
- IconLeftWidget:
- icon: "language-python"
+ OneLineIconListItem(
+ IconLeftWidget(
+ icon="language-python"
+ ),
+ text="Single-line item with avatar"
+ )
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/OneLineIconListItem.png
:align: center
@@ -243,14 +477,30 @@ OneLineIconListItem
TwoLineIconListItem
-------------------
-.. code-block:: kv
+.. tabs::
- TwoLineIconListItem:
- text: "Two-line item with avatar"
- secondary_text: "Secondary text here"
+ .. tab:: Declarative KV style
+
+ .. code-block:: kv
+
+ TwoLineIconListItem:
+ text: "Two-line item with avatar"
+ secondary_text: "Secondary text here"
+
+ IconLeftWidget:
+ icon: "language-python"
- IconLeftWidget:
- icon: "language-python"
+ .. tab:: Declarative python style
+
+ .. code-block:: python
+
+ TwoLineIconListItem(
+ IconLeftWidget(
+ icon="language-python"
+ ),
+ text="Single-line item with avatar",
+ secondary_text: "Secondary text here"
+ )
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/TwoLineIconListItem.png
:align: center
@@ -259,15 +509,32 @@ TwoLineIconListItem
ThreeLineIconListItem
---------------------
-.. code-block:: kv
+.. tabs::
- ThreeLineIconListItem:
- text: "Three-line item with avatar"
- secondary_text: "Secondary text here"
- tertiary_text: "fit more text than usual"
+ .. tab:: Declarative KV style
- IconLeftWidget:
- icon: "language-python"
+ .. code-block:: kv
+
+ ThreeLineIconListItem:
+ text: "Three-line item with avatar"
+ secondary_text: "Secondary text here"
+ tertiary_text: "fit more text than usual"
+
+ IconLeftWidget:
+ icon: "language-python"
+
+ .. tab:: Declarative python style
+
+ .. code-block:: python
+
+ ThreeLineIconListItem(
+ IconLeftWidget(
+ icon="language-python"
+ ),
+ text="Single-line item with avatar",
+ secondary_text: "Secondary text here",
+ tertiary_text: "fit more text than usual",
+ )
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/ThreeLineIconListItem.png
:align: center
@@ -276,16 +543,34 @@ ThreeLineIconListItem
OneLineAvatarIconListItem
-------------------------
-.. code-block:: kv
+.. tabs::
+
+ .. tab:: Declarative KV style
- OneLineAvatarIconListItem:
- text: "One-line item with avatar"
+ .. code-block:: kv
- IconLeftWidget:
- icon: "plus"
+ OneLineAvatarIconListItem:
+ text: "One-line item with avatar"
+
+ IconLeftWidget:
+ icon: "plus"
- IconRightWidget:
- icon: "minus"
+ IconRightWidget:
+ icon: "minus"
+
+ .. tab:: Declarative python style
+
+ .. code-block:: python
+
+ OneLineAvatarIconListItem(
+ IconLeftWidget(
+ icon="plus"
+ ),
+ IconRightWidget(
+ icon="minus"
+ ),
+ text="Single-line item with avatar",
+ )
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/OneLineAvatarIconListItem.png
:align: center
@@ -294,17 +579,36 @@ OneLineAvatarIconListItem
TwoLineAvatarIconListItem
-------------------------
-.. code-block:: kv
+.. tabs::
- TwoLineAvatarIconListItem:
- text: "Two-line item with avatar"
- secondary_text: "Secondary text here"
+ .. tab:: Declarative KV style
+
+ .. code-block:: kv
+
+ TwoLineAvatarIconListItem:
+ text: "Two-line item with avatar"
+ secondary_text: "Secondary text here"
+
+ IconLeftWidget:
+ icon: "plus"
+
+ IconRightWidget:
+ icon: "minus"
+
+ .. tab:: Declarative python style
- IconLeftWidget:
- icon: "plus"
+ .. code-block:: python
- IconRightWidget:
- icon: "minus"
+ TwoLineAvatarIconListItem(
+ IconLeftWidget(
+ icon="plus"
+ ),
+ IconRightWidget(
+ icon="minus"
+ ),
+ text="Single-line item with avatar",
+ secondary_text: "Secondary text here",
+ )
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/TwoLineAvatarIconListItem.png
:align: center
@@ -313,18 +617,38 @@ TwoLineAvatarIconListItem
ThreeLineAvatarIconListItem
---------------------------
-.. code-block:: kv
+.. tabs::
- ThreeLineAvatarIconListItem:
- text: "Three-line item with avatar"
- secondary_text: "Secondary text here"
- tertiary_text: "fit more text than usual"
+ .. tab:: Declarative KV style
+
+ .. code-block:: kv
+
+ ThreeLineAvatarIconListItem:
+ text: "Three-line item with avatar"
+ secondary_text: "Secondary text here"
+ tertiary_text: "fit more text than usual"
- IconLeftWidget:
- icon: "plus"
+ IconLeftWidget:
+ icon: "plus"
+
+ IconRightWidget:
+ icon: "minus"
+
+ .. tab:: Declarative python style
- IconRightWidget:
- icon: "minus"
+ .. code-block:: python
+
+ ThreeLineAvatarIconListItem(
+ IconLeftWidget(
+ icon="plus"
+ ),
+ IconRightWidget(
+ icon="minus"
+ ),
+ text="Single-line item with avatar",
+ secondary_text: "Secondary text here",
+ tertiary_text: "fit more text than usual",
+ )
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/ThreeLineAvatarIconListItem.png
:align: center
@@ -332,101 +656,196 @@ ThreeLineAvatarIconListItem
Custom list item
----------------
-.. code-block:: python
+.. tabs::
+
+ .. tab:: Declarative KV style
- from kivy.lang import Builder
- from kivy.properties import StringProperty
+ .. code-block:: python
- from kivymd.app import MDApp
- from kivymd.uix.list import IRightBodyTouch, OneLineAvatarIconListItem
- from kivymd.uix.selectioncontrol import MDCheckbox
- from kivymd.icon_definitions import md_icons
+ from kivy.lang import Builder
+ from kivy.properties import StringProperty
+ from kivymd.app import MDApp
+ from kivymd.uix.list import IRightBodyTouch, OneLineAvatarIconListItem
+ from kivymd.uix.selectioncontrol import MDCheckbox
+ from kivymd.icon_definitions import md_icons
- KV = '''
- <ListItemWithCheckbox>:
- IconLeftWidget:
- icon: root.icon
+ KV = '''
+ <ListItemWithCheckbox>:
+
+ IconLeftWidget:
+ icon: root.icon
+
+ RightCheckbox:
+
+
+ MDScrollView:
+
+ MDList:
+ id: scroll
+ '''
+
+
+ class ListItemWithCheckbox(OneLineAvatarIconListItem):
+ '''Custom list item.'''
+
+ icon = StringProperty("android")
+
+
+ class RightCheckbox(IRightBodyTouch, MDCheckbox):
+ '''Custom right container.'''
- RightCheckbox:
+ class Example(MDApp):
+ def build(self):
+ self.theme_cls.theme_style = "Dark"
+ return Builder.load_string(KV)
- MDBoxLayout:
+ def on_start(self):
+ icons = list(md_icons.keys())
+ for i in range(30):
+ self.root.ids.scroll.add_widget(
+ ListItemWithCheckbox(text=f"Item {i}", icon=icons[i])
+ )
- ScrollView:
- MDList:
- id: scroll
- '''
+ Example().run()
+ .. tab:: Declarative python style
- class ListItemWithCheckbox(OneLineAvatarIconListItem):
- '''Custom list item.'''
+ .. code-block:: python
- icon = StringProperty("android")
+ from kivymd.app import MDApp
+ from kivymd.uix.list import IRightBodyTouch, OneLineAvatarIconListItem
+ from kivymd.uix.selectioncontrol import MDCheckbox
+ from kivymd.uix.scrollview import MDScrollView
+ from kivymd.uix.list import MDList
+ from kivymd.icon_definitions import md_icons
- class RightCheckbox(IRightBodyTouch, MDCheckbox):
- '''Custom right container.'''
+ class RightCheckbox(IRightBodyTouch, MDCheckbox):
+ '''Custom right container.'''
- class MainApp(MDApp):
- def build(self):
- return Builder.load_string(KV)
+ class Example(MDApp):
+ def build(self):
+ self.theme_cls.theme_style = "Dark"
+ return (
+ MDScrollView(
+ MDList(
+ id="scroll"
+ )
+ )
+ )
- def on_start(self):
- icons = list(md_icons.keys())
- for i in range(30):
- self.root.ids.scroll.add_widget(
- ListItemWithCheckbox(text=f"Item {i}", icon=icons[i])
- )
+ def on_start(self):
+ icons = list(md_icons.keys())
+ for i in range(30):
+ self.root.ids.scroll.add_widget(
+ OneLineAvatarIconListItem(
+ IconLeftWidget(
+ icon=icons[i]
+ ),
+ RightCheckbox(),
+ text=f"Item {i}",
+ )
+ )
- MainApp().run()
+ Example().run()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/custom-list-item.png
:align: center
-.. code-block:: python
+.. tabs::
+
+ .. tab:: Declarative KV style
+
+ .. code-block:: python
+
+ from kivy.lang import Builder
+
+ from kivymd.app import MDApp
+ from kivymd.uix.boxlayout import MDBoxLayout
+ from kivymd.uix.list import IRightBodyTouch
+
+ KV = '''
+ OneLineAvatarIconListItem:
+ text: "One-line item with avatar"
+ on_size:
+ self.ids._right_container.width = container.width
+ self.ids._right_container.x = container.width
+
+ IconLeftWidget:
+ icon: "cog"
+
+ YourContainer:
+ id: container
+
+ MDIconButton:
+ icon: "minus"
+
+ MDIconButton:
+ icon: "plus"
+ '''
- from kivy.lang import Builder
- from kivymd.app import MDApp
- from kivymd.uix.boxlayout import MDBoxLayout
- from kivymd.uix.list import IRightBodyTouch
+ class YourContainer(IRightBodyTouch, MDBoxLayout):
+ adaptive_width = True
- KV = '''
- OneLineAvatarIconListItem:
- text: "One-line item with avatar"
- on_size:
- self.ids._right_container.width = container.width
- self.ids._right_container.x = container.width
- IconLeftWidget:
- icon: "cog"
+ class Example(MDApp):
+ def build(self):
+ self.theme_cls.theme_style = "Dark"
+ return Builder.load_string(KV)
- YourContainer:
- id: container
- MDIconButton:
- icon: "minus"
+ Example().run()
- MDIconButton:
- icon: "plus"
- '''
+ .. tab:: Declarative python style
+ .. code-block:: python
- class YourContainer(IRightBodyTouch, MDBoxLayout):
- adaptive_width = True
+ from kivymd.app import MDApp
+ from kivymd.uix.boxlayout import MDBoxLayout
+ from kivymd.uix.list import IRightBodyTouch
+ from kivymd.uix.button import MDIconButton
+ from kivymd.uix.list import OneLineAvatarIconListItem, IconLeftWidget
- class MainApp(MDApp):
- def build(self):
- return Builder.load_string(KV)
+ class YourContainer(IRightBodyTouch, MDBoxLayout):
+ adaptive_width = True
- MainApp().run()
+ class Example(MDApp):
+ def build(self):
+ self.theme_cls.theme_style = "Dark"
+ return (
+ OneLineAvatarIconListItem(
+ IconLeftWidget(
+ icon="cog"
+ ),
+ YourContainer(
+ MDIconButton(
+ icon="minus"
+ ),
+ MDIconButton(
+ icon="plus"
+ ),
+ id="container"
+ ),
+ text="One-line item with avatar"
+ )
+ )
+
+ def on_start(self):
+ container = self.root.ids.container
+ self.root.ids._right_container.width = container.width
+ container.x = container.width
+
+
+ Example().run()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/custom-list-right-container.png
:align: center
@@ -437,26 +856,56 @@ Behavior
When using the `AvatarListItem` and `IconListItem` classes, when an icon is clicked,
the event of this icon is triggered:
-.. code-block:: kv
+.. tabs::
+
+ .. tab:: Declarative KV style
- OneLineIconListItem:
- text: "Single-line item with icon"
+ .. code-block:: kv
- IconLeftWidget:
- icon: "language-python"
+ OneLineIconListItem:
+ text: "Single-line item with icon"
+
+ IconLeftWidget:
+ icon: "language-python"
+
+ .. tab:: Declarative python style
+
+ .. code-block:: python
+
+ OneLineIconListItem(
+ IconLeftWidget(
+ icon="language-python"
+ ),
+ text="Single-line item with avatar",
+ )
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/list-icon-trigger.gif
:align: center
You can disable the icon event using the `WithoutTouch` classes:
-.. code-block:: kv
+.. tabs::
- OneLineIconListItem:
- text: "Single-line item with icon"
+ .. tab:: Declarative KV style
- IconLeftWidgetWithoutTouch:
- icon: "language-python"
+ .. code-block:: kv
+
+ OneLineIconListItem:
+ text: "Single-line item with icon"
+
+ IconLeftWidgetWithoutTouch:
+ icon: "language-python"
+
+ .. tab:: Declarative python style
+
+ .. code-block:: python
+
+ OneLineIconListItem(
+ IconLeftWidgetWithoutTouch(
+ icon="language-python"
+ ),
+ text="Single-line item with avatar",
+ )
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/list-icon-without-trigger.gif
:align: center
@@ -539,13 +988,9 @@ class MDList(MDGridLayout):
_list_vertical_padding = NumericProperty("8dp")
- def add_widget(self, widget, index=0, canvas=None):
- super().add_widget(widget, index, canvas)
- self.height += widget.height
-
- def remove_widget(self, widget):
- super().remove_widget(widget)
- self.height -= widget.height
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.adaptive_height = True
class BaseListItem(
@@ -569,8 +1014,8 @@ class BaseListItem(
text_color = ColorProperty(None)
"""
- Text color in ``rgba`` format used if :attr:`~theme_text_color` is set
- to `'Custom'`.
+ Text color in (r, g, b, a) or string format used
+ if :attr:`~theme_text_color` is set to `'Custom'`.
:attr:`text_color` is a :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
@@ -578,7 +1023,9 @@ class BaseListItem(
font_style = StringProperty("Subtitle1")
"""
- Text font style. See ``kivymd.font_definitions.py``.
+ Text font style.
+ See `font-definitions <https://kivymd.readthedocs.io/en/latest/themes/font-definitions/>`_
+ for more information.
:attr:`font_style` is a :class:`~kivy.properties.StringProperty`
and defaults to `'Subtitle1'`.
@@ -586,7 +1033,7 @@ class BaseListItem(
theme_text_color = StringProperty("Primary", allownone=True)
"""
- Theme text color in ``rgba`` format for primary text.
+ The name of the color scheme for for the primary text.
:attr:`theme_text_color` is a :class:`~kivy.properties.StringProperty`
and defaults to `'Primary'`.
@@ -610,7 +1057,7 @@ class BaseListItem(
secondary_text_color = ColorProperty(None)
"""
- Text color in ``rgba`` format used for secondary text
+ Text color in (r, g, b, a) or string format used for secondary text
if :attr:`~secondary_theme_text_color` is set to `'Custom'`.
:attr:`secondary_text_color` is a :class:`~kivy.properties.ColorProperty`
@@ -619,7 +1066,7 @@ class BaseListItem(
tertiary_text_color = ColorProperty(None)
"""
- Text color in ``rgba`` format used for tertiary text
+ Text color in (r, g, b, a) or string format used for tertiary text
if :attr:`~tertiary_theme_text_color` is set to 'Custom'.
:attr:`tertiary_text_color` is a :class:`~kivy.properties.ColorProperty`
@@ -628,7 +1075,7 @@ class BaseListItem(
secondary_theme_text_color = StringProperty("Secondary", allownone=True)
"""
- Theme text color for secondary text.
+ The name of the color scheme for for the secondary text.
:attr:`secondary_theme_text_color` is a :class:`~kivy.properties.StringProperty`
and defaults to `'Secondary'`.
@@ -636,7 +1083,7 @@ class BaseListItem(
tertiary_theme_text_color = StringProperty("Secondary", allownone=True)
"""
- Theme text color for tertiary text.
+ The name of the color scheme for for the tertiary text.
:attr:`tertiary_theme_text_color` is a :class:`~kivy.properties.StringProperty`
and defaults to `'Secondary'`.
@@ -644,7 +1091,9 @@ class BaseListItem(
secondary_font_style = StringProperty("Body1")
"""
- Font style for secondary line. See ``kivymd.font_definitions.py``.
+ Font style for secondary line.
+ See `font-definitions <https://kivymd.readthedocs.io/en/latest/themes/font-definitions/>`_
+ for more information.
:attr:`secondary_font_style` is a :class:`~kivy.properties.StringProperty`
and defaults to `'Body1'`.
@@ -652,7 +1101,9 @@ class BaseListItem(
tertiary_font_style = StringProperty("Body1")
"""
- Font style for tertiary line. See ``kivymd.font_definitions.py``.
+ Font style for tertiary line.
+ See `font-definitions <https://kivymd.readthedocs.io/en/latest/themes/font-definitions/>`_
+ for more information.
:attr:`tertiary_font_style` is a :class:`~kivy.properties.StringProperty`
and defaults to `'Body1'`.
@@ -671,7 +1122,7 @@ class BaseListItem(
divider_color = ColorProperty(None)
"""
- Divider color.
+ Divider color in (r, g, b, a) or string format.
.. versionadded:: 1.0.0
@@ -681,7 +1132,7 @@ class BaseListItem(
bg_color = ColorProperty(None)
"""
- Background color for menu item.
+ Background color for list item in (r, g, b, a) or string format.
:attr:`bg_color` is a :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
diff --git a/sbapp/kivymd/uix/menu/menu.kv b/sbapp/kivymd/uix/menu/menu.kv
index d4ac30ec..f72f87d4 100644
--- a/sbapp/kivymd/uix/menu/menu.kv
+++ b/sbapp/kivymd/uix/menu/menu.kv
@@ -1,4 +1,4 @@
-#:import STD_INC kivymd.material_resources.STANDARD_INCREMENT
+#:import STANDARD_INCREMENT kivymd.material_resources.STANDARD_INCREMENT
<RightContent>
@@ -14,7 +14,7 @@
<MDMenu>
size_hint: None, None
- width: root.width_mult * STD_INC
+ width: root.width_mult * STANDARD_INCREMENT
bar_width: 0
key_viewclass: "viewclass"
key_size: "height"
@@ -28,7 +28,7 @@
orientation: "vertical"
-<MenuContainer@MDCard+FakeRectangularElevationBehavior>
+<MenuContainer@MDCard>
<MDDropdownMenu>
diff --git a/sbapp/kivymd/uix/menu/menu.py b/sbapp/kivymd/uix/menu/menu.py
index ebe14617..102cf96f 100755
--- a/sbapp/kivymd/uix/menu/menu.py
+++ b/sbapp/kivymd/uix/menu/menu.py
@@ -781,7 +781,7 @@ class MDDropdownMenu(ThemableBehavior, FloatLayout):
and defaults to `'[dp(7)]'`.
"""
- elevation = NumericProperty(10)
+ elevation = NumericProperty(4)
"""
Elevation value of menu dialog.
@@ -790,7 +790,7 @@ class MDDropdownMenu(ThemableBehavior, FloatLayout):
.. code-block:: python
self.menu = MDDropdownMenu(
- elevation=16,
+ elevation=4,
...,
)
@@ -798,7 +798,7 @@ class MDDropdownMenu(ThemableBehavior, FloatLayout):
:align: center
:attr:`elevation` is an :class:`~kivy.properties.NumericProperty`
- and defaults to `10`.
+ and defaults to `4`.
"""
_start_coords = []
diff --git a/sbapp/kivymd/uix/navigationdrawer/navigationdrawer.py b/sbapp/kivymd/uix/navigationdrawer/navigationdrawer.py
index d3158695..9ac5823f 100755
--- a/sbapp/kivymd/uix/navigationdrawer/navigationdrawer.py
+++ b/sbapp/kivymd/uix/navigationdrawer/navigationdrawer.py
@@ -61,7 +61,7 @@ A simple example
MDTopAppBar:
title: "Navigation Drawer"
- elevation: 10
+ elevation: 4
pos_hint: {"top": 1}
md_bg_color: "#e7e4c0"
specific_text_color: "#4a4939"
@@ -115,7 +115,7 @@ A simple example
MDScreen(
MDTopAppBar(
title="Navigation Drawer",
- elevation=10,
+ elevation=4,
pos_hint={"top": 1},
md_bg_color="#e7e4c0",
specific_text_color="#4a4939",
@@ -188,7 +188,7 @@ Standard content for the navigation bar
MDTopAppBar:
title: "Navigation Drawer"
- elevation: 10
+ elevation: 4
pos_hint: {"top": 1}
md_bg_color: "#e7e4c0"
specific_text_color: "#4a4939"
@@ -296,7 +296,7 @@ Standard content for the navigation bar
MDScreen(
MDTopAppBar(
title="Navigation Drawer",
- elevation=10,
+ elevation=4,
pos_hint={"top": 1},
md_bg_color="#e7e4c0",
specific_text_color="#4a4939",
@@ -396,7 +396,7 @@ Switching screens in the ``ScreenManager`` and using the common ``MDTopAppBar``
MDTopAppBar:
pos_hint: {"top": 1}
- elevation: 10
+ elevation: 4
title: "MDNavigationDrawer"
left_action_items: [["menu", lambda x: nav_drawer.set_state("open")]]
@@ -465,7 +465,7 @@ Switching screens in the ``ScreenManager`` and using the common ``MDTopAppBar``
MDScreen(
MDTopAppBar(
pos_hint={"top": 1},
- elevation=10,
+ elevation=4,
title="MDNavigationDrawer",
left_action_items=[["menu", lambda x: self.nav_drawer_open()]],
),
@@ -551,14 +551,9 @@ from kivy.properties import (
StringProperty,
VariableListProperty,
)
-from kivy.uix.floatlayout import FloatLayout
from kivy.uix.screenmanager import ScreenManager
from kivymd import uix_path
-from kivymd.uix.behaviors import (
- DeclarativeBehavior,
- FakeRectangularElevationBehavior,
-)
from kivymd.uix.behaviors.focus_behavior import FocusBehavior
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.card import MDCard
@@ -1029,7 +1024,7 @@ class MDNavigationDrawerMenu(MDScrollView):
widget.text_color = widget._text_color
-class MDNavigationDrawer(MDCard, FakeRectangularElevationBehavior):
+class MDNavigationDrawer(MDCard):
type = OptionProperty("modal", options=("standard", "modal"))
"""
Type of drawer. Modal type will be on top of screen. Standard type will be
diff --git a/sbapp/kivymd/uix/navigationrail/navigationrail.py b/sbapp/kivymd/uix/navigationrail/navigationrail.py
index 8d0acdc0..42e2e247 100644
--- a/sbapp/kivymd/uix/navigationrail/navigationrail.py
+++ b/sbapp/kivymd/uix/navigationrail/navigationrail.py
@@ -29,45 +29,86 @@ Usage
MDNavigationRailItem:
-.. code-block:: python
+.. tabs::
- from kivy.lang import Builder
+ .. tab:: Declarative KV style
- from kivymd.app import MDApp
+ .. code-block:: python
- KV = '''
+ from kivy.lang import Builder
+ from kivymd.app import MDApp
- MDBoxLayout:
+ KV = '''
+ MDBoxLayout:
- MDNavigationRail:
+ MDNavigationRail:
- MDNavigationRailItem:
- text: "Python"
- icon: "language-python"
+ MDNavigationRailItem:
+ text: "Python"
+ icon: "language-python"
- MDNavigationRailItem:
- text: "JavaScript"
- icon: "language-javascript"
+ MDNavigationRailItem:
+ text: "JavaScript"
+ icon: "language-javascript"
- MDNavigationRailItem:
- text: "CPP"
- icon: "language-cpp"
+ MDNavigationRailItem:
+ text: "CPP"
+ icon: "language-cpp"
- MDNavigationRailItem:
- text: "Git"
- icon: "git"
+ MDNavigationRailItem:
+ text: "Git"
+ icon: "git"
- MDScreen:
- '''
+ MDScreen:
+ '''
- class Example(MDApp):
- def build(self):
- return Builder.load_string(KV)
+ class Example(MDApp):
+ def build(self):
+ return Builder.load_string(KV)
+
+
+ Example().run()
+
+ .. tab:: Declarative python style
+
+ .. code-block:: python
+
+ from kivymd.app import MDApp
+ from kivymd.uix.boxlayout import MDBoxLayout
+ from kivymd.uix.navigationrail import MDNavigationRail, MDNavigationRailItem
+
+
+ class Example(MDApp):
+ def build(self):
+ self.theme_cls.theme_style = "Dark"
+ self.theme_cls.primary_palette = "Orange"
+ return (
+ MDBoxLayout(
+ MDNavigationRail(
+ MDNavigationRailItem(
+ text="Python",
+ icon="language-python",
+ ),
+ MDNavigationRailItem(
+ text="JavaScript",
+ icon="language-javascript",
+ ),
+ MDNavigationRailItem(
+ text="CPP",
+ icon="language-cpp",
+ ),
+ MDNavigationRailItem(
+ text="Git",
+ icon="git",
+ ),
+ )
+ )
+ )
- Example().run()
+ Example().run()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-usage.png
:align: center
@@ -75,202 +116,412 @@ Usage
Example
=======
-.. code-block:: python
+.. tabs::
- from kivy.clock import Clock
- from kivy.lang import Builder
+ .. tab:: Declarative KV and imperative python styles
- from kivymd.app import MDApp
- from kivymd.uix.behaviors import RoundedRectangularElevationBehavior
- from kivymd.uix.boxlayout import MDBoxLayout
- from kivymd.uix.button import MDFillRoundFlatIconButton
- from kivymd.uix.label import MDLabel
- from kivymd.uix.screen import MDScreen
+ .. code-block:: python
- KV = '''
- #:import FadeTransition kivy.uix.screenmanager.FadeTransition
+ from kivy.clock import Clock
+ from kivy.lang import Builder
+ from kivymd.app import MDApp
+ from kivymd.uix.behaviors import CommonElevationBehavior
+ from kivymd.uix.boxlayout import MDBoxLayout
+ from kivymd.uix.button import MDFillRoundFlatIconButton
+ from kivymd.uix.label import MDLabel
+ from kivymd.uix.screen import MDScreen
- <ExtendedButton>
- elevation: 3
- -height: "56dp"
+ KV = '''
+ #:import FadeTransition kivy.uix.screenmanager.FadeTransition
- <DrawerClickableItem@MDNavigationDrawerItem>
- focus_color: "#e7e4c0"
- unfocus_color: "#fffcf4"
+ <ExtendedButton>
+ elevation: 3.5
+ shadow_radius: 12
+ shadow_softness: 4
+ -height: "56dp"
- MDScreen:
+ <DrawerClickableItem@MDNavigationDrawerItem>
+ focus_color: "#e7e4c0"
+ unfocus_color: "#fffcf4"
- MDNavigationLayout:
- ScreenManager:
+ MDScreen:
- MDScreen:
+ MDNavigationLayout:
+
+ ScreenManager:
+
+ MDScreen:
+
+ MDBoxLayout:
+ orientation: "vertical"
+
+ MDBoxLayout:
+ adaptive_height: True
+ md_bg_color: "#fffcf4"
+ padding: "12dp"
+
+ MDLabel:
+ text: "12:00"
+ adaptive_height: True
+ pos_hint: {"center_y": .5}
+
+ MDBoxLayout:
+
+ MDNavigationRail:
+ id: navigation_rail
+ md_bg_color: "#fffcf4"
+ selected_color_background: "#e7e4c0"
+ ripple_color_item: "#e7e4c0"
+ on_item_release: app.switch_screen(*args)
+
+ MDNavigationRailMenuButton:
+ on_release: nav_drawer.set_state("open")
+
+ MDNavigationRailFabButton:
+ md_bg_color: "#b0f0d6"
+
+ MDNavigationRailItem:
+ text: "Python"
+ icon: "language-python"
+
+ MDNavigationRailItem:
+ text: "JavaScript"
+ icon: "language-javascript"
+
+ MDNavigationRailItem:
+ text: "CPP"
+ icon: "language-cpp"
+
+ MDNavigationRailItem:
+ text: "Swift"
+ icon: "language-swift"
+
+ ScreenManager:
+ id: screen_manager
+ transition:
+ FadeTransition(duration=.2, clearcolor=app.theme_cls.bg_dark)
- MDBoxLayout:
- orientation: "vertical"
+ MDNavigationDrawer:
+ id: nav_drawer
+ radius: (0, 16, 16, 0)
+ md_bg_color: "#fffcf4"
+ elevation: 4
+ width: "240dp"
+
+ MDNavigationDrawerMenu:
MDBoxLayout:
+ orientation: "vertical"
adaptive_height: True
- md_bg_color: "#fffcf4"
- padding: "12dp"
+ spacing: "12dp"
+ padding: "3dp", 0, 0, "12dp"
- MDLabel:
- text: "12:00"
- adaptive_height: True
- pos_hint: {"center_y": .5}
+ MDIconButton:
+ icon: "menu"
- MDBoxLayout:
+ ExtendedButton:
+ text: "Compose"
+ icon: "pencil"
- MDNavigationRail:
- id: navigation_rail
- md_bg_color: "#fffcf4"
- selected_color_background: "#e7e4c0"
- ripple_color_item: "#e7e4c0"
- on_item_release: app.switch_screen(*args)
-
- MDNavigationRailMenuButton:
- on_release: nav_drawer.set_state("open")
+ DrawerClickableItem:
+ text: "Python"
+ icon: "language-python"
- MDNavigationRailFabButton:
- md_bg_color: "#b0f0d6"
-
- MDNavigationRailItem:
- text: "Python"
- icon: "language-python"
-
- MDNavigationRailItem:
- text: "JavaScript"
- icon: "language-javascript"
-
- MDNavigationRailItem:
- text: "CPP"
- icon: "language-cpp"
-
- MDNavigationRailItem:
- text: "Swift"
- icon: "language-swift"
-
- ScreenManager:
- id: screen_manager
- transition:
- FadeTransition(duration=.2, clearcolor=app.theme_cls.bg_dark)
-
- MDNavigationDrawer:
- id: nav_drawer
- radius: (0, 16, 16, 0)
- md_bg_color: "#fffcf4"
- elevation: 12
- width: "240dp"
-
- MDNavigationDrawerMenu:
-
- MDBoxLayout:
- orientation: "vertical"
- adaptive_height: True
- spacing: "12dp"
- padding: 0, 0, 0, "12dp"
-
- MDIconButton:
- icon: "menu"
-
- ExtendedButton:
- text: "Compose"
- icon: "pencil"
-
- DrawerClickableItem:
- text: "Python"
- icon: "language-python"
-
- DrawerClickableItem:
- text: "JavaScript"
- icon: "language-javascript"
-
- DrawerClickableItem:
- text: "CPP"
- icon: "language-cpp"
-
- DrawerClickableItem:
- text: "Swift"
- icon: "language-swift"
- '''
-
-
- class ExtendedButton(
- RoundedRectangularElevationBehavior, MDFillRoundFlatIconButton
- ):
- '''
- Implements a button of type
- `Extended FAB <https://m3.material.io/components/extended-fab/overview>`_.
-
- .. rubric::
- Extended FABs help people take primary actions.
- They're wider than FABs to accommodate a text label and larger target
- area.
-
- This type of buttons is not yet implemented in the standard widget set
- of the KivyMD library, so we will implement it ourselves in this class.
- '''
-
- def __init__(self, **kwargs):
- super().__init__(**kwargs)
- self.padding = "16dp"
- Clock.schedule_once(self.set_spacing)
-
- def set_spacing(self, interval):
- self.ids.box.spacing = "12dp"
-
- def set_radius(self, *args):
- if self.rounded_button:
- self._radius = self.radius = self.height / 4
-
-
- class Example(MDApp):
- def build(self):
- self.theme_cls.material_style = "M3"
- self.theme_cls.primary_palette = "Orange"
- return Builder.load_string(KV)
-
- def switch_screen(
- self, instance_navigation_rail, instance_navigation_rail_item
- ):
- '''
- Called when tapping on rail menu items. Switches application screens.
+ DrawerClickableItem:
+ text: "JavaScript"
+ icon: "language-javascript"
+
+ DrawerClickableItem:
+ text: "CPP"
+ icon: "language-cpp"
+
+ DrawerClickableItem:
+ text: "Swift"
+ icon: "language-swift"
'''
- self.root.ids.screen_manager.current = (
- instance_navigation_rail_item.icon.split("-")[1].lower()
- )
- def on_start(self):
- '''Creates application screens.'''
+ class ExtendedButton(MDFillRoundFlatIconButton, CommonElevationBehavior):
+ '''
+ Implements a button of type
+ `Extended FAB <https://m3.material.io/components/extended-fab/overview>`_.
- navigation_rail_items = self.root.ids.navigation_rail.get_items()[:]
- navigation_rail_items.reverse()
+ .. rubric::
+ Extended FABs help people take primary actions.
+ They're wider than FABs to accommodate a text label and larger target
+ area.
+
+ This type of buttons is not yet implemented in the standard widget set
+ of the KivyMD library, so we will implement it ourselves in this class.
+ '''
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.padding = "16dp"
+ Clock.schedule_once(self.set_spacing)
+
+ def set_spacing(self, interval):
+ self.ids.box.spacing = "12dp"
+
+ def set_radius(self, *args):
+ if self.rounded_button:
+ self._radius = self.radius = self.height / 4
- for widget in navigation_rail_items:
- name_screen = widget.icon.split("-")[1].lower()
- screen = MDScreen(
- name=name_screen,
- md_bg_color="#edd769",
- radius=[18, 0, 0, 0],
- )
- box = MDBoxLayout(padding="12dp")
- label = MDLabel(
- text=name_screen.capitalize(),
- font_style="H1",
- halign="right",
- adaptive_height=True,
- shorten=True,
- )
- box.add_widget(label)
- screen.add_widget(box)
- self.root.ids.screen_manager.add_widget(screen)
+ class Example(MDApp):
+ def build(self):
+ self.theme_cls.material_style = "M3"
+ self.theme_cls.primary_palette = "Orange"
+ return Builder.load_string(KV)
+
+ def switch_screen(
+ self, instance_navigation_rail, instance_navigation_rail_item
+ ):
+ '''
+ Called when tapping on rail menu items. Switches application screens.
+ '''
+
+ self.root.ids.screen_manager.current = (
+ instance_navigation_rail_item.icon.split("-")[1].lower()
+ )
+
+ def on_start(self):
+ '''Creates application screens.'''
+
+ navigation_rail_items = self.root.ids.navigation_rail.get_items()[:]
+ navigation_rail_items.reverse()
+
+ for widget in navigation_rail_items:
+ name_screen = widget.icon.split("-")[1].lower()
+ screen = MDScreen(
+ name=name_screen,
+ md_bg_color="#edd769",
+ radius=[18, 0, 0, 0],
+ )
+ box = MDBoxLayout(padding="12dp")
+ label = MDLabel(
+ text=name_screen.capitalize(),
+ font_style="H1",
+ halign="right",
+ adaptive_height=True,
+ shorten=True,
+ )
+ box.add_widget(label)
+ screen.add_widget(box)
+ self.root.ids.screen_manager.add_widget(screen)
+
+
+ Example().run()
+
+ .. tab:: Declarative python style
+
+ .. code-block:: python
+
+ from kivy.clock import Clock
+ from kivy.metrics import dp
+
+ from kivymd.app import MDApp
+ from kivymd.uix.behaviors import CommonElevationBehavior
+ from kivymd.uix.boxlayout import MDBoxLayout
+ from kivymd.uix.button import MDFillRoundFlatIconButton, MDIconButton
+ from kivymd.uix.label import MDLabel
+ from kivymd.uix.navigationdrawer import (
+ MDNavigationDrawerItem,
+ MDNavigationLayout,
+ MDNavigationDrawer,
+ MDNavigationDrawerMenu,
+ )
+ from kivymd.uix.navigationrail import (
+ MDNavigationRail,
+ MDNavigationRailMenuButton,
+ MDNavigationRailFabButton,
+ MDNavigationRailItem,
+ )
+ from kivymd.uix.screen import MDScreen
+ from kivymd.uix.screenmanager import MDScreenManager
+
+
+ class DrawerClickableItem(MDNavigationDrawerItem):
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.focus_color = "#e7e4c0"
+ self.unfocus_color = self.theme_cls.bg_light
+ self.radius = 24
+
+
+ class ExtendedButton(MDFillRoundFlatIconButton, CommonElevationBehavior):
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.padding = "16dp"
+ self.elevation = 3.5
+ self.shadow_radius = 12
+ self.shadow_softness = 4
+ self.height = dp(56)
+ Clock.schedule_once(self.set_spacing)
+
+ def set_spacing(self, interval):
+ self.ids.box.spacing = "12dp"
+
+ def set_radius(self, *args):
+ if self.rounded_button:
+ self._radius = self.radius = self.height / 4
+
+
+ class Example(MDApp):
+ def build(self):
+ self.theme_cls.material_style = "M3"
+ self.theme_cls.primary_palette = "Orange"
+ return MDScreen(
+ MDNavigationLayout(
+ MDScreenManager(
+ MDScreen(
+ MDBoxLayout(
+ MDBoxLayout(
+ MDLabel(
+ text="12:00",
+ adaptive_height=True,
+ pos_hint={"center_y": 0.5},
+ ),
+ adaptive_height=True,
+ md_bg_color="#fffcf4",
+ padding="12dp",
+ ),
+ MDBoxLayout(
+ MDNavigationRail(
+ MDNavigationRailMenuButton(
+ on_release=self.open_nav_drawer,
+ ),
+ MDNavigationRailFabButton(
+ md_bg_color="#b0f0d6",
+ ),
+ MDNavigationRailItem(
+ text="Python",
+ icon="language-python",
+ ),
+ MDNavigationRailItem(
+ text="JavaScript",
+ icon="language-javascript",
+ ),
+ MDNavigationRailItem(
+ text="CPP",
+ icon="language-cpp",
+ ),
+ MDNavigationRailItem(
+ text="Swift",
+ icon="language-swift",
+ ),
+ id="navigation_rail",
+ md_bg_color="#fffcf4",
+ selected_color_background="#e7e4c0",
+ ripple_color_item="#e7e4c0",
+ ),
+ MDScreenManager(
+ id="screen_manager_content",
+ ),
+ id="root_box",
+ ),
+ id="box_rail",
+ orientation="vertical",
+ ),
+ id="box",
+ ),
+ id="screen",
+ ),
+ id="screen_manager",
+ ),
+ MDNavigationDrawer(
+ MDNavigationDrawerMenu(
+ MDBoxLayout(
+ MDIconButton(
+ icon="menu",
+ ),
+ ExtendedButton(
+ text="Compose",
+ icon="pencil",
+ ),
+ orientation="vertical",
+ adaptive_height=True,
+ spacing="12dp",
+ padding=("3dp", 0, 0, "12dp"),
+ ),
+ DrawerClickableItem(
+ text="Python",
+ icon="language-python",
+ ),
+ DrawerClickableItem(
+ text="JavaScript",
+ icon="language-javascript",
+ ),
+ DrawerClickableItem(
+ text="CPP",
+ icon="language-cpp",
+ ),
+ DrawerClickableItem(
+ text="Swift",
+ icon="language-swift",
+ ),
+ ),
+ id="nav_drawer",
+ radius=(0, 16, 16, 0),
+ elevation=4,
+ width="240dp",
+ ),
+ )
+
+ def switch_screen(self, *args, screen_manager_content=None):
+ '''
+ Called when tapping on rail menu items. Switches application screens.
+ '''
+
+ instance_navigation_rail, instance_navigation_rail_item = args
+ screen_manager_content.current = (
+ instance_navigation_rail_item.icon.split("-")[1].lower()
+ )
+
+ def open_nav_drawer(self, *args):
+ self.root.ids.nav_drawer.set_state("open")
+
+ def on_start(self):
+ '''Creates application screens.'''
+
+ screen_manager = self.root.ids.screen_manager
+ root_box = screen_manager.ids.screen.ids.box.ids.box_rail.ids.root_box
+ navigation_rail = root_box.ids.navigation_rail
+ screen_manager_content = root_box.ids.screen_manager_content
+ navigation_rail_items = navigation_rail.get_items()[:]
+ navigation_rail_items.reverse()
+ navigation_rail.bind(
+ on_item_release=lambda *args: self.switch_screen(
+ *args, screen_manager_content=screen_manager_content
+ )
+ )
- Example().run()
+ for widget in navigation_rail_items:
+ name_screen = widget.icon.split("-")[1].lower()
+ screen_manager_content.add_widget(
+ MDScreen(
+ MDBoxLayout(
+ MDLabel(
+ text=name_screen.capitalize(),
+ font_style="H1",
+ halign="right",
+ adaptive_height=True,
+ shorten=True,
+ ),
+ padding="12dp",
+ ),
+ name=name_screen,
+ md_bg_color="#edd769",
+ radius=[18, 0, 0, 0],
+ ),
+ )
+
+
+ Example().run()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-example.gif
:align: center
@@ -306,12 +557,11 @@ from kivy.uix.behaviors import ButtonBehavior
from kivymd import uix_path
from kivymd.theming import ThemableBehavior
-from kivymd.uix.behaviors import FakeRectangularElevationBehavior
+from kivymd.uix.behaviors import ScaleBehavior
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.button import MDFloatingActionButton, MDIconButton
from kivymd.uix.card import MDCard
from kivymd.uix.floatlayout import MDFloatLayout
-from kivymd.uix.templates import ScaleWidget
from kivymd.uix.widget import MDWidget
with open(
@@ -333,7 +583,7 @@ class PanelItems(MDBoxLayout):
"""Box for menu items."""
-class RippleWidget(MDWidget, ScaleWidget):
+class RippleWidget(MDWidget, ScaleBehavior):
"""
Implements a background color for a menu item -
(:class:`~MDNavigationRailItem`).
@@ -562,7 +812,7 @@ class MDNavigationRailItem(ThemableBehavior, ButtonBehavior, MDBoxLayout):
self.navigation_rail.dispatch("on_item_release", self)
-class MDNavigationRail(MDCard, FakeRectangularElevationBehavior):
+class MDNavigationRail(MDCard):
"""
:Events:
:attr:`on_item_press`
diff --git a/sbapp/kivymd/uix/pickers/datepicker/datepicker.kv b/sbapp/kivymd/uix/pickers/datepicker/datepicker.kv
index 8f95e586..13835dee 100644
--- a/sbapp/kivymd/uix/pickers/datepicker/datepicker.kv
+++ b/sbapp/kivymd/uix/pickers/datepicker/datepicker.kv
@@ -307,34 +307,6 @@
else (dp(32), dp(32))
disabled: True
- canvas:
- Color:
- rgba:
- ( \
- ( \
- self.theme_cls.primary_color if not root.owner.selector_color \
- else root.owner.selector_color \
- ) \
- if root.is_selected and not self.disabled \
- else (0, 0, 0, 0) \
- ) \
- if self.owner.mode != "range" else \
- ( \
- ( \
- self.theme_cls.primary_color if not root.owner.selector_color \
- else root.owner.selector_color \
- ) \
- if root.is_selected and not self.disabled \
- and (self.owner.mode == "range" and self.owner._start_range_date) \
- else (0, 0, 0, 0) \
- )
- Ellipse:
- size:
- (dp(42), dp(42)) \
- if root.theme_cls.device_orientation == "portrait" \
- else (dp(32), dp(32))
- pos: self.pos
-
# Fill marking the available dates of the range, if using the `range` mode
# or use `min_date/max_date`.
canvas.before:
@@ -355,12 +327,14 @@
if root.theme_cls.device_orientation == "portrait" \
else \
(dp(32), dp(28)) \
- if self.index in [6, 13, 20, 27, 30] or self.owner._date_range \
+ if self.index in [6, 13, 20, 27, 34] or self.owner._date_range \
and self.text and self.owner._date_range[-1] == date( \
self.current_year, \
self.current_month, \
int(self.text) \
) \
+ or self.text and int(self.text) == \
+ calendar.monthrange(self.current_year, self.current_month)[1] \
else (dp(46), dp(28))
pos:
(self.x - dp(1.5), self.y + dp(5)) \
@@ -395,29 +369,15 @@
else [0, 0, 0, 0]) \
)
- # Circle marking the beginning and end of the date range if the "range"
- # mode is used.
+ # Selection circle.
Color:
rgba:
- [0, 0, 0, 0] if not self.owner._date_range else \
- (
( \
self.theme_cls.primary_color if not root.owner.selector_color \
else root.owner.selector_color \
) \
- if self.text and self.owner._date_range[0] == date( \
- self.current_year, \
- self.current_month, \
- int(self.text) \
- ) \
- or \
- self.text and self.owner._date_range[-1] == date( \
- self.current_year, \
- self.current_month, \
- int(self.text) \
- ) \
- else (0, 0, 0, 0) \
- )
+ if root.is_selected and not self.disabled \
+ else (0, 0, 0, 0)
Ellipse:
size:
(dp(42), dp(42)) \
diff --git a/sbapp/kivymd/uix/pickers/datepicker/datepicker.py b/sbapp/kivymd/uix/pickers/datepicker/datepicker.py
index 50e80dee..3188408c 100644
--- a/sbapp/kivymd/uix/pickers/datepicker/datepicker.py
+++ b/sbapp/kivymd/uix/pickers/datepicker/datepicker.py
@@ -11,65 +11,110 @@ Components/DatePicker
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/picker-previous.png
:align: center
-.. warning:: The widget is under testing. Therefore, we would be grateful if
- you would let us know about the bugs found.
-
.. rubric:: Usage
-.. code-block:: python
+.. tabs::
- from kivy.lang import Builder
+ .. tab:: Declarative KV style
- from kivymd.app import MDApp
- from kivymd.uix.pickers import MDDatePicker
+ .. code-block:: python
- KV = '''
- MDFloatLayout:
+ from kivy.lang import Builder
- MDTopAppBar:
- title: "MDDatePicker"
- pos_hint: {"top": 1}
- elevation: 10
+ from kivymd.app import MDApp
+ from kivymd.uix.pickers import MDDatePicker
- MDRaisedButton:
- text: "Open date picker"
- pos_hint: {'center_x': .5, 'center_y': .5}
- on_release: app.show_date_picker()
- '''
+ KV = '''
+ MDFloatLayout:
+ MDRaisedButton:
+ text: "Open date picker"
+ pos_hint: {'center_x': .5, 'center_y': .5}
+ on_release: app.show_date_picker()
+ '''
- class Test(MDApp):
- def build(self):
- return Builder.load_string(KV)
- def on_save(self, instance, value, date_range):
- '''
- Events called when the "OK" dialog box button is clicked.
+ class Test(MDApp):
+ def build(self):
+ self.theme_cls.theme_style = "Dark"
+ self.theme_cls.primary_palette = "Orange"
+ return Builder.load_string(KV)
- :type instance: <kivymd.uix.picker.MDDatePicker object>;
+ def on_save(self, instance, value, date_range):
+ '''
+ Events called when the "OK" dialog box button is clicked.
- :param value: selected date;
- :type value: <class 'datetime.date'>;
+ :type instance: <kivymd.uix.picker.MDDatePicker object>;
+ :param value: selected date;
+ :type value: <class 'datetime.date'>;
+ :param date_range: list of 'datetime.date' objects in the selected range;
+ :type date_range: <class 'list'>;
+ '''
- :param date_range: list of 'datetime.date' objects in the selected range;
- :type date_range: <class 'list'>;
- '''
+ print(instance, value, date_range)
- print(instance, value, date_range)
+ def on_cancel(self, instance, value):
+ '''Events called when the "CANCEL" dialog box button is clicked.'''
- def on_cancel(self, instance, value):
- '''Events called when the "CANCEL" dialog box button is clicked.'''
+ def show_date_picker(self):
+ date_dialog = MDDatePicker()
+ date_dialog.bind(on_save=self.on_save, on_cancel=self.on_cancel)
+ date_dialog.open()
- def show_date_picker(self):
- date_dialog = MDDatePicker()
- date_dialog.bind(on_save=self.on_save, on_cancel=self.on_cancel)
- date_dialog.open()
+ Test().run()
+
+ .. tab:: Declarative python style
+
+ .. code-block:: python
+
+ from kivymd.app import MDApp
+ from kivymd.uix.button import MDRaisedButton
+ from kivymd.uix.pickers import MDDatePicker
+ from kivymd.uix.screen import MDScreen
+
+
+ class Test(MDApp):
+ def build(self):
+ self.theme_cls.theme_style = "Dark"
+ self.theme_cls.primary_palette = "Orange"
+ return (
+ MDScreen(
+ MDRaisedButton(
+ text="Open data picker",
+ pos_hint={'center_x': .5, 'center_y': .5},
+ on_release=self.show_date_picker,
+ )
+ )
+ )
+
+ def on_save(self, instance, value, date_range):
+ '''
+ Events called when the "OK" dialog box button is clicked.
+
+ :type instance: <kivymd.uix.picker.MDDatePicker object>;
+
+ :param value: selected date;
+ :type value: <class 'datetime.date'>;
+
+ :param date_range: list of 'datetime.date' objects in the selected range;
+ :type date_range: <class 'list'>;
+ '''
+
+ print(instance, value, date_range)
+
+ def on_cancel(self, instance, value):
+ '''Events called when the "CANCEL" dialog box button is clicked.'''
+
+ def show_date_picker(self, *args):
+ date_dialog = MDDatePicker()
+ date_dialog.bind(on_save=self.on_save, on_cancel=self.on_cancel)
+ date_dialog.open()
- Test().run()
+ Test().run()
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDDatePicker.gif
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDDatePicker.png
:align: center
Open date dialog with the specified date
@@ -81,7 +126,7 @@ Open date dialog with the specified date
date_dialog = MDDatePicker(year=1983, month=4, day=12)
date_dialog.open()
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/previous-date.png
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/specified-date.png
:align: center
Interval date
@@ -94,12 +139,16 @@ that are not included in this range will have the status `disabled`.
def show_date_picker(self):
date_dialog = MDDatePicker(
- min_date=datetime.date(2021, 2, 15),
- max_date=datetime.date(2021, 3, 27),
+ min_date=datetime.date.today(),
+ max_date=datetime.date(
+ datetime.date.today().year,
+ datetime.date.today().month,
+ datetime.date.today().day + 2,
+ ),
)
date_dialog.open()
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/range-date.gif
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/range-date.png
:align: center
The range of available dates can be changed in the picker dialog:
@@ -122,7 +171,7 @@ You can set the range of years using the :attr:`~kivymd.uix.picker.MDDatePicker.
.. code-block:: python
def show_date_picker(self):
- date_dialog = MDDatePicker(min_year=2021, max_year=2030)
+ date_dialog = MDDatePicker(min_year=2022, max_year=2030)
date_dialog.open()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/min-max-year-date.png
@@ -141,18 +190,21 @@ Set and select a date range
:align: center
"""
+from __future__ import annotations
+
__all__ = ("MDDatePicker", "BaseDialogPicker", "DatePickerInputField")
import calendar
import datetime
+import math
import os
import time
from datetime import date
+from itertools import zip_longest
from typing import Union
from kivy import Logger
from kivy.animation import Animation
-from kivy.clock import Clock
from kivy.lang import Builder
from kivy.metrics import dp
from kivy.properties import (
@@ -175,7 +227,7 @@ from kivymd.theming import ThemableBehavior, ThemeManager
from kivymd.toast import toast
from kivymd.uix.behaviors import (
CircularRippleBehavior,
- FakeRectangularElevationBehavior,
+ CommonElevationBehavior,
SpecificBackgroundColorBehavior,
)
from kivymd.uix.boxlayout import MDBoxLayout
@@ -194,7 +246,7 @@ with open(
class BaseDialogPicker(
BaseDialog,
- FakeRectangularElevationBehavior,
+ CommonElevationBehavior,
SpecificBackgroundColorBehavior,
):
"""
@@ -255,11 +307,11 @@ class BaseDialogPicker(
primary_color = ColorProperty(None)
"""
- Background color of toolbar in (r, g, b, a) format.
+ Background color of toolbar in (r, g, b, a) or string format.
.. code-block:: python
- MDDatePicker(primary_color=get_color_from_hex("#72225b"))
+ MDDatePicker(primary_color="brown")
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/primary-color-date.png
:align: center
@@ -270,13 +322,13 @@ class BaseDialogPicker(
accent_color = ColorProperty(None)
"""
- Background color of calendar/clock face in (r, g, b, a) format.
+ Background color of calendar/clock face in (r, g, b, a) or string format.
.. code-block:: python
MDDatePicker(
- primary_color=get_color_from_hex("#72225b"),
- accent_color=get_color_from_hex("#5d1a4a"),
+ primary_color="brown",
+ accent_color="darkred",
)
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/accent-color-date.png
@@ -288,14 +340,15 @@ class BaseDialogPicker(
selector_color = ColorProperty(None)
"""
- Background color of the selected day of the month or hour in (r, g, b, a) format.
+ Background color of the selected day of the month or hour in (r, g, b, a)
+ or string format.
.. code-block:: python
MDDatePicker(
- primary_color=get_color_from_hex("#72225b"),
- accent_color=get_color_from_hex("#5d1a4a"),
- selector_color=get_color_from_hex("#e93f39"),
+ primary_color="brown",
+ accent_color="darkred",
+ selector_color="red",
)
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/selector-color-date.png
@@ -307,15 +360,15 @@ class BaseDialogPicker(
text_toolbar_color = ColorProperty(None)
"""
- Color of labels for text on a toolbar in (r, g, b, a) format.
+ Color of labels for text on a toolbar in (r, g, b, a) or string format.
.. code-block:: python
MDDatePicker(
- primary_color=get_color_from_hex("#72225b"),
- accent_color=get_color_from_hex("#5d1a4a"),
- selector_color=get_color_from_hex("#e93f39"),
- text_toolbar_color=get_color_from_hex("#cccccc"),
+ primary_color="brown",
+ accent_color="darkred",
+ selector_color="red",
+ text_toolbar_color="lightgrey",
)
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-toolbar-color-date.png
@@ -327,16 +380,16 @@ class BaseDialogPicker(
text_color = ColorProperty(None)
"""
- Color of text labels in calendar/clock face in (r, g, b, a) format.
+ Color of text labels in calendar/clock face in (r, g, b, a) or string format.
.. code-block:: python
MDDatePicker(
- primary_color=get_color_from_hex("#72225b"),
- accent_color=get_color_from_hex("#5d1a4a"),
- selector_color=get_color_from_hex("#e93f39"),
- text_toolbar_color=get_color_from_hex("#cccccc"),
- text_color=("#ffffff"),
+ primary_color="brown",
+ accent_color="darkred",
+ selector_color="red",
+ text_toolbar_color="lightgrey",
+ text_color="orange",
)
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-color-date.png
@@ -348,17 +401,18 @@ class BaseDialogPicker(
text_current_color = ColorProperty(None)
"""
- Color of the text of the current day of the month/hour in (r, g, b, a) format.
+ Color of the text of the current day of the month/hour in (r, g, b, a)
+ or string format.
.. code-block:: python
MDDatePicker(
- primary_color=get_color_from_hex("#72225b"),
- accent_color=get_color_from_hex("#5d1a4a"),
- selector_color=get_color_from_hex("#e93f39"),
- text_toolbar_color=get_color_from_hex("#cccccc"),
- text_color=("#ffffff"),
- text_current_color=get_color_from_hex("#e93f39"),
+ primary_color="brown",
+ accent_color="darkred",
+ selector_color="red",
+ text_toolbar_color="lightgrey",
+ text_color="orange",
+ text_current_color="white",
)
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-current-color-date.png
@@ -375,13 +429,13 @@ class BaseDialogPicker(
.. code-block:: python
MDDatePicker(
- primary_color=get_color_from_hex("#72225b"),
- accent_color=get_color_from_hex("#5d1a4a"),
- selector_color=get_color_from_hex("#e93f39"),
- text_toolbar_color=get_color_from_hex("#cccccc"),
- text_color=("#ffffff"),
- text_current_color=get_color_from_hex("#e93f39"),
- text_button_color=(1, 1, 1, .5),
+ primary_color="brown",
+ accent_color="darkred",
+ selector_color="red",
+ text_toolbar_color="lightgrey",
+ text_color="orange",
+ text_current_color="white",
+ text_button_color="lightgrey",
)
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-button-color-date.png
@@ -391,52 +445,124 @@ class BaseDialogPicker(
and defaults to `None`.
"""
- input_field_background_color = ColorProperty(None)
+ input_field_background_color_normal = ColorProperty(None)
"""
- Background color of input fields in (r, g, b, a) format.
+ Background color normal of input fields in (r, g, b, a) or string format.
+
+ .. versionadded:: 1.1.0
.. code-block:: python
MDDatePicker(
- primary_color=get_color_from_hex("#72225b"),
- accent_color=get_color_from_hex("#5d1a4a"),
- selector_color=get_color_from_hex("#e93f39"),
- text_toolbar_color=get_color_from_hex("#cccccc"),
- text_color=("#ffffff"),
- text_current_color=get_color_from_hex("#e93f39"),
- input_field_background_color=(1, 1, 1, 0.2),
+ primary_color="brown",
+ accent_color="darkred",
+ selector_color="red",
+ text_toolbar_color="lightgrey",
+ text_color="orange",
+ text_current_color="white",
+ text_button_color="lightgrey",
+ input_field_background_color_normal="coral",
)
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/input-field-background-color-date.png
:align: center
- :attr:`input_field_background_color` is an :class:`~kivy.properties.ColorProperty`
+ :attr:`input_field_background_color_normal` is an :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
"""
+ input_field_background_color_focus = ColorProperty(None)
+ """
+ Background color normal of input fields in (r, g, b, a) or string format.
+
+ .. versionadded:: 1.1.0
+
+ .. code-block:: python
+
+ MDDatePicker(
+ primary_color="brown",
+ accent_color="darkred",
+ selector_color="red",
+ text_toolbar_color="lightgrey",
+ text_color="orange",
+ text_current_color="white",
+ text_button_color="lightgrey",
+ input_field_background_color_normal="coral",
+ input_field_background_color_focus="red",
+ )
+
+ .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/input-field-background-color-focus-date.png
+ :align: center
+
+ :attr:`input_field_background_color_focus` is an :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
+
+ input_field_background_color = ColorProperty(None)
+ """
+ .. deprecated:: 1.1.0
+ Use :attr:`input_field_background_color_normal` instead.
+ """
+
input_field_text_color = ColorProperty(None)
"""
- Text color of input fields in (r, g, b, a) format.
+ .. deprecated:: 1.1.0
+ Use :attr:`input_field_text_color_normal` instead.
+ """
- Background color of input fields.
+ input_field_text_color_normal = ColorProperty(None)
+ """
+ Text color normal of input fields in (r, g, b, a) or string format.
+
+ .. versionadded:: 1.1.0
.. code-block:: python
MDDatePicker(
- primary_color=get_color_from_hex("#72225b"),
- accent_color=get_color_from_hex("#5d1a4a"),
- selector_color=get_color_from_hex("#e93f39"),
- text_toolbar_color=get_color_from_hex("#cccccc"),
- text_color=("#ffffff"),
- text_current_color=get_color_from_hex("#e93f39"),
- input_field_background_color=(1, 1, 1, 0.2),
- input_field_text_color=(1, 1, 1, 1),
+ primary_color="brown",
+ accent_color="darkred",
+ selector_color="red",
+ text_toolbar_color="lightgrey",
+ text_color="orange",
+ text_current_color="white",
+ text_button_color="lightgrey",
+ input_field_background_color_normal="brown",
+ input_field_background_color_focus="red",
+ input_field_text_color_normal="white",
)
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/input-field-background-color-date.png
+ .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/input-field-text-color-normal-date.png
:align: center
- :attr:`input_field_text_color` is an :class:`~kivy.properties.ColorProperty`
+ :attr:`input_field_text_color_normal` is an :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
+
+ input_field_text_color_focus = ColorProperty(None)
+ """
+ Text color focus of input fields in (r, g, b, a) or string format.
+
+ .. versionadded:: 1.1.0
+
+ .. code-block:: python
+
+ MDDatePicker(
+ primary_color="brown",
+ accent_color="darkred",
+ selector_color="red",
+ text_toolbar_color="lightgrey",
+ text_color="orange",
+ text_current_color="white",
+ text_button_color="lightgrey",
+ input_field_background_color_normal="brown",
+ input_field_background_color_focus="red",
+ input_field_text_color_normal="white",
+ )
+
+ .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/input-field-text-color-normal-date.png
+ :align: center
+
+ :attr:`input_field_text_color_focus` is an :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
"""
@@ -447,16 +573,18 @@ class BaseDialogPicker(
.. code-block:: python
MDDatePicker(
- primary_color=get_color_from_hex("#72225b"),
- accent_color=get_color_from_hex("#5d1a4a"),
- selector_color=get_color_from_hex("#e93f39"),
- text_toolbar_color=get_color_from_hex("#cccccc"),
- text_color=("#ffffff"),
- text_current_color=get_color_from_hex("#e93f39"),
- input_field_background_color=(1, 1, 1, 0.2),
- input_field_text_color=(1, 1, 1, 1),
- font_name="Weather.ttf",
-
+ primary_color="brown",
+ accent_color="darkred",
+ selector_color="red",
+ text_toolbar_color="lightgrey",
+ text_color="orange",
+ text_current_color="white",
+ text_button_color="lightgrey",
+ input_field_background_color_normal="brown",
+ input_field_background_color_focus="red",
+ input_field_text_color_normal="white",
+ input_field_text_color_focus="lightgrey",
+ font_name="nasalization.ttf",
)
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/font-name-date.png
@@ -471,6 +599,20 @@ class BaseDialogPicker(
self.register_event_type("on_save")
self.register_event_type("on_cancel")
+ def on_input_field_background_color(
+ self, instance, value: str | list | tuple
+ ) -> None:
+ """For supported of current API."""
+
+ self.input_field_background_color_normal = value
+
+ def on_input_field_text_color(
+ self, instance, value: str | list | tuple
+ ) -> None:
+ """For supported of current API."""
+
+ self.input_field_text_color_normal = value
+
def on_save(self, *args) -> None:
"""Events called when the "OK" dialog box button is clicked."""
@@ -606,13 +748,18 @@ class DatePickerDaySelectableItem(
self.owner.set_selected_widget(self)
+ def on_touch_down(self, touch):
+ # If year_layout is active don't dispatch on_touch_down events,
+ # so date items don't consume touch.
+ if not self.owner.ids._year_layout.disabled:
+ return
+ super().on_touch_down(touch)
+
class DatePickerYearSelectableItem(RecycleDataViewBehavior, MDLabel):
"""Implements an item for a pick list of the year."""
index = None
- selected = BooleanProperty(False)
- selectable = BooleanProperty(True)
selected_color = ColorProperty([0, 0, 0, 0])
owner = ObjectProperty()
@@ -623,7 +770,7 @@ class DatePickerYearSelectableItem(RecycleDataViewBehavior, MDLabel):
def on_touch_down(self, touch):
if super().on_touch_down(touch):
return True
- if self.collide_point(*touch.pos) and self.selectable:
+ if self.collide_point(*touch.pos):
self.owner.year = int(self.text)
# self.owner.sel_year = self.owner.year
self.owner.ids.label_full_date.text = self.owner.set_text_full_date(
@@ -635,7 +782,6 @@ class DatePickerYearSelectableItem(RecycleDataViewBehavior, MDLabel):
return self.parent.select_with_touch(self.index, touch)
def apply_selection(self, table_data, index, is_selected):
- self.selected = is_selected
if is_selected:
self.selected_color = (
self.owner.selector_color
@@ -661,7 +807,7 @@ class DatePickerYearSelectableItem(RecycleDataViewBehavior, MDLabel):
class MDDatePicker(BaseDialogPicker):
text_weekday_color = ColorProperty(None)
"""
- Text color of weekday names in (r, g, b, a) format.
+ Text color of weekday names in (r, g, b, a) or string format.
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-date-picker-text-weekday-color.png
:align: center
@@ -813,7 +959,6 @@ class MDDatePicker(BaseDialogPicker):
_enter_data_field_two = None
_enter_data_field_container = None
_date_range = []
- _sel_day_widget = ObjectProperty()
_scale_calendar_layout = NumericProperty(1)
_scale_year_layout = NumericProperty(0)
_shift_dialog_height = NumericProperty(0)
@@ -838,11 +983,6 @@ class MDDatePicker(BaseDialogPicker):
self.month = self.sel_month
self.year = self.sel_year
self.day = self.sel_day
- self._current_selected_date = (
- self.sel_day,
- self.sel_month,
- self.sel_year,
- )
super().__init__(**kwargs)
self.theme_cls.bind(device_orientation=self.on_device_orientation)
@@ -861,16 +1001,6 @@ class MDDatePicker(BaseDialogPicker):
self.generate_list_widgets_days()
self.update_calendar(self.sel_year, self.sel_month)
- if (
- not self.max_date
- and not self.min_date
- and not self._date_range
- and self.mode != "range"
- ):
- # Mark the current day.
- self.set_month_day(self.sel_day)
- self._sel_day_widget.dispatch("on_release")
-
def on_device_orientation(
self, instance_theme_manager: ThemeManager, orientation_value: str
) -> None:
@@ -924,18 +1054,14 @@ class MDDatePicker(BaseDialogPicker):
Animation(opacity=1, d=0.15).start(self.ids.chevron_left)
Animation(opacity=1, d=0.15).start(self.ids.chevron_right)
Animation(_scale_year_layout=0, d=0.15).start(self)
- Animation(
- _shift_dialog_height=dp(0), _scale_calendar_layout=1, d=0.15
- ).start(self)
+ Animation(_scale_calendar_layout=1, d=0.15).start(self)
- self._calendar_layout.clear_widgets()
- self.generate_list_widgets_days()
+ # Move selection to the same day and month of the selected year.
+ self.sel_year = self.year
+ last_day = calendar.monthrange(self.year, self.sel_month)[1]
+ self.sel_day = min(self.sel_day, last_day)
self.update_calendar(self.year, self.month)
- if self.mode != "range":
- self.set_month_day(self.day)
- self._sel_day_widget.dispatch("on_release")
-
def transformation_to_dialog_select_year(self) -> None:
def disabled_chevron_buttons(*args):
self.ids.chevron_left.disabled = True
@@ -943,15 +1069,20 @@ class MDDatePicker(BaseDialogPicker):
self._select_year_dialog_open = True
self.ids._year_layout.disabled = False
- self._scale_calendar_layout = 0
Animation(opacity=0, d=0.15).start(self.ids.chevron_left)
Animation(opacity=0, d=0.15).start(self.ids.chevron_right)
+ Animation(_scale_calendar_layout=0, d=0.15).start(self)
anim = Animation(_scale_year_layout=1, d=0.15)
anim.bind(on_complete=disabled_chevron_buttons)
anim.start(self)
self.ids.triangle.icon = "menu-up"
self.generate_list_widgets_years()
self.set_position_to_current_year()
+ if self.min_year <= self.year < self.max_year:
+ index = self.year - self.min_year
+ self.ids._year_layout.children[0].select_node(index)
+ else:
+ self.ids._year_layout.children[0].clear_selection()
def transformation_to_dialog_input_date(self) -> None:
def set_date_to_input_field():
@@ -1063,13 +1194,10 @@ class MDDatePicker(BaseDialogPicker):
if not self.min_date and not self.max_date:
list_date = self._enter_data_field.get_list_date()
if len(list_date) == 3 and len(list_date[2]) == 4:
- # self._sel_day_widget.is_selected = False
- self.update_calendar(int(list_date[2]), int(list_date[1]))
- self.set_month_day(int(list_date[0]))
- # self._sel_day_widget.dispatch("on_release")
- if self.mode != "range":
- self._sel_day_widget.is_selected = False
- self._sel_day_widget.dispatch("on_release")
+ self.sel_day = int(list_date[0])
+ self.sel_month = int(list_date[1])
+ self.sel_year = int(list_date[2])
+ self.update_calendar(self.sel_year, self.sel_month)
elif self.min_date and self.max_date:
list_min_date = self._enter_data_field.get_list_date()
list_max_date = self._enter_data_field_two.get_list_date()
@@ -1107,8 +1235,6 @@ class MDDatePicker(BaseDialogPicker):
def update_calendar_for_date_range(self) -> None:
# self.compare_date_range()
self._date_range = self.get_date_range()
- self._calendar_layout.clear_widgets()
- self.generate_list_widgets_days()
self.update_calendar(self.year, self.month)
def update_text_full_date(self, list_date) -> None:
@@ -1140,79 +1266,72 @@ class MDDatePicker(BaseDialogPicker):
)
def update_calendar(self, year, month) -> None:
- try:
- dates = [x for x in self.calendar.itermonthdates(year, month)]
- except ValueError as e:
- if str(e) == "year is out of range":
- pass
+ self.year, self.month = year, month
+ if self.mode == "picker":
+ selected_date = date(self.sel_year, self.sel_month, self.sel_day)
+ selected_dates = {selected_date}
else:
- self.year = year
- self.month = month
- for idx in range(len(self._calendar_list)):
- self._calendar_list[idx].current_month = int(self.month)
- self._calendar_list[idx].current_year = int(self.year)
-
- # Dates of the month not in the range 1-31.
- if idx >= len(dates) or dates[idx].month != month:
- # self._calendar_list[idx].disabled = True
- self._calendar_list[idx].text = ""
- # Dates of the month in the range 1-31.
- else:
- self._calendar_list[idx].disabled = False
- self._calendar_list[idx].text = str(dates[idx].day)
- self._calendar_list[idx].is_today = dates[idx] == self.today
- # The marked date widget has a True value in the `is_selected`
- # attribute. In the KV file it is checked if the date widget
- # (DatePickerDaySelectableItem) has the `is_selected = False`
- # attribute value, then the date widget is not highlighted.
- if (
- 0
- if not self._calendar_list[idx].text
- else int(self._calendar_list[idx].text),
- self._calendar_list[idx].current_month,
- self._calendar_list[idx].current_year,
- ) == self._current_selected_date:
- self._calendar_list[idx].is_selected = True
- else:
- self._calendar_list[idx].is_selected = False
- # Dates outside the set range - disabled.
- if (
- self.mode == "picker"
- and self._date_range
- and self._calendar_list[idx].text
- ) or (
- self.mode == "range"
- and self._start_range_date
- and self._end_range_date
- and self._calendar_list[idx].text
- ):
- if (
- date(
- self._calendar_list[idx].current_year,
- self._calendar_list[idx].current_month,
- int(self._calendar_list[idx].text),
- )
- not in self._date_range
- ):
- self._calendar_list[idx].disabled = True
+ selected_dates = {self._start_range_date, self._end_range_date}
+ dates = self.calendar.itermonthdates(year, month)
+ for widget, widget_date in zip_longest(self._calendar_list, dates):
+ # Only widgets whose dates are in the displayed month are visible.
+ visible = (
+ widget_date is not None
+ and widget_date.month == month
+ and widget_date.year == year
+ )
+ widget.text = str(widget_date.day) if visible else ""
+ widget.current_year = year
+ widget.current_month = month
+ widget.is_today = visible and widget_date == self.today
+ widget.is_selected = visible and widget_date in selected_dates
+ # I don't understand why, but this line is important. Without this
+ # line, some widgets that we are trying to disable remain enabled.
+ widget.disabled = False
+ widget.disabled = (
+ not visible
+ or self.mode == "range"
+ and self._date_range
+ and widget_date not in self._date_range
+ )
def get_field(self) -> MDTextField:
"""Creates and returns a text field object used to enter dates."""
if issubclass(self.input_field_cls, MDTextField):
+ text_color_focus = (
+ self.input_field_text_color_focus
+ if self.input_field_text_color_focus
+ else self.theme_cls.primary_color
+ )
+ text_color_normal = (
+ self.input_field_text_color_normal
+ if self.input_field_text_color_normal
+ else self.theme_cls.disabled_hint_text_color
+ )
+ fill_color_focus = (
+ self.input_field_background_color_focus
+ if self.input_field_background_color_focus
+ else self.theme_cls.bg_dark
+ )
+ fill_color_normal = (
+ self.input_field_background_color_normal
+ if self.input_field_background_color_normal
+ else self.theme_cls.bg_darkest
+ )
+
field = self.input_field_cls(
owner=self,
helper_text=self.helper_text,
- line_color_normal=self.theme_cls.divider_color,
- )
- field.color_mode = "custom"
- field.line_color_focus = (
- self.theme_cls.primary_color
- if not self.input_field_text_color
- else self.input_field_text_color
+ fill_color_normal=fill_color_normal,
+ fill_color_focus=fill_color_focus,
+ hint_text_color_normal=text_color_normal,
+ hint_text_color_focus=text_color_focus,
+ text_color_normal=text_color_normal,
+ text_color_focus=text_color_focus,
+ line_color_focus=text_color_focus,
+ line_color_normal=text_color_normal,
)
- field.current_hint_text_color = field.line_color_focus
- field._current_hint_text_color = field.line_color_focus
return field
else:
raise TypeError(
@@ -1239,10 +1358,7 @@ class MDDatePicker(BaseDialogPicker):
"set_text_full_date:\n\t" f"Month [{month}] out of range."
)
if int(day) > calendar.monthrange(int(year), (month))[1]:
- raise ValueError(
- "set_text_full_date:\n\t"
- f"Day [{day}] out of range for the month {month}"
- )
+ return ""
date = datetime.date(int(year), int(month), int(day))
separator = (
"\n"
@@ -1345,46 +1461,55 @@ class MDDatePicker(BaseDialogPicker):
)
def set_selected_widget(self, widget) -> None:
- if self._sel_day_widget:
- self._sel_day_widget.is_selected = False
-
- widget.is_selected = True
- self.sel_month = int(self.month)
- self.sel_year = int(self.year)
+ self.sel_year = self.year
+ self.sel_month = self.month
self.sel_day = int(widget.text)
- self._current_selected_date = (
- self.sel_day,
- self.sel_month,
- self.sel_year,
- )
- self._sel_day_widget = widget
+ self.update_calendar(self.sel_year, self.sel_month)
def set_month_day(self, day) -> None:
- for idx in range(len(self._calendar_list)):
- if str(day) == str(self._calendar_list[idx].text):
- self._sel_day_widget = self._calendar_list[idx]
- self.sel_day = int(self._calendar_list[idx].text)
- if self._sel_day_widget:
- self._sel_day_widget.is_selected = False
- self._sel_day_widget = self._calendar_list[idx]
+ # This method is no longer used. The code bellow repeats the behavior
+ # that was previously required of it for backward compatibility
+ # reasons.
+ self.sel_day = day
+ self.update_calendar(self.sel_year, self.sel_month)
def set_position_to_current_year(self) -> None:
- # TODO: Add the feature to set the position of the list of years
- # for the current year. This is not currently possible because the
- # ``RecycleView`` class does not support this functionality.
- # There is a solution to this problem
- # - https://github.com/Bakterija/log_fruit/blob/dev/src/app_modules/widgets/app_recycleview/recycleview.py.
- # But I have not been able to get it to work.
- pass
+ year_layout = self.ids._year_layout
+ # When this method is called for the first time, RecycleView has not
+ # yet added widgets to the year list, so we use the default height.
+ widget_height = year_layout.children[0].default_size[1]
+ cols_amount = year_layout.children[0].cols
+ rows_amount = math.ceil((self.max_year - self.min_year) / cols_amount)
+ row_index = (self.year - self.min_year) // cols_amount
+ # To find the middle of the current year widget, we add the height of
+ # the rows under this widget with half the widget height.
+ widget_center_y = (rows_amount - row_index - 1 + 0.5) * widget_height
+ viewport_height = year_layout.height
+ year_list_height = rows_amount * widget_height
+ # If there are too few years in the list to fill the entire viewport,
+ # RecycleView displays additional empty space outside the list.
+ # We have to move the viewport up so that this space is displayed
+ # under the years list. Also, this guard condition protects against
+ # the division by zero error below.
+ if viewport_height >= year_list_height:
+ year_layout.scroll_y = 1
+ return
+ viewport_bottom = widget_center_y - 0.5 * viewport_height
+ # We set scroll_y property to the ratio of the actual lifting height
+ # of the viewport to the maximum possible, and clamp this ratio in the
+ # range from 0 to 1 so that the viewport still is in a valid position
+ # if it is impossible to show the widget in the middle.
+ scroll_y = viewport_bottom / (year_list_height - viewport_height)
+ year_layout.scroll_y = min(1, max(0, scroll_y))
def generate_list_widgets_years(self) -> None:
+ self.ids._year_layout.data = []
for i, number_year in enumerate(range(self.min_year, self.max_year)):
self.ids._year_layout.data.append(
{
"owner": self,
"text": str(number_year),
"index": i,
- "selectable": True,
"viewclass": "DatePickerYearSelectableItem",
}
)
@@ -1416,26 +1541,9 @@ class MDDatePicker(BaseDialogPicker):
Called when "chevron-left" and "chevron-right" buttons are pressed.
Switches the calendar to the previous/next month.
"""
-
- operation = 1 if operation == "next" else -1
- month = (
- 12
- if self.month + operation == 0
- else 1
- if self.month + operation == 13
- else self.month + operation
- )
- year = (
- self.year - 1
- if self.month + operation == 0
- else self.year + 1
- if self.month + operation == 13
- else self.year
- )
+ month_delta = 1 if operation == "next" else -1
+ year = self.year + (self.month - 1 + month_delta) // 12
+ month = (self.month - 1 + month_delta) % 12 + 1
+ if year <= 0:
+ year, month = 1, 1
self.update_calendar(year, month)
- if self.sel_day:
- x = calendar.monthrange(year, month)[1]
- if x < self.sel_day:
- self.sel_day = (
- x if year <= self.sel_year and month <= self.sel_year else 1
- )
diff --git a/sbapp/kivymd/uix/pickers/timepicker/timepicker.py b/sbapp/kivymd/uix/pickers/timepicker/timepicker.py
index cb1e8759..2b646d80 100644
--- a/sbapp/kivymd/uix/pickers/timepicker/timepicker.py
+++ b/sbapp/kivymd/uix/pickers/timepicker/timepicker.py
@@ -16,35 +16,73 @@ Components/TimePicker
.. rubric:: Usage
-.. code-block::
+.. tabs::
- from kivy.lang import Builder
+ .. tab:: Declarative KV style
- from kivymd.app import MDApp
- from kivymd.uix.pickers import MDTimePicker
+ .. code-block:: python
- KV = '''
- MDFloatLayout:
+ from kivy.lang import Builder
- MDRaisedButton:
- text: "Open time picker"
- pos_hint: {'center_x': .5, 'center_y': .5}
- on_release: app.show_time_picker()
- '''
+ from kivymd.app import MDApp
+ from kivymd.uix.pickers import MDTimePicker
+ KV = '''
+ MDFloatLayout:
- class Test(MDApp):
- def build(self):
- return Builder.load_string(KV)
+ MDRaisedButton:
+ text: "Open time picker"
+ pos_hint: {'center_x': .5, 'center_y': .5}
+ on_release: app.show_time_picker()
+ '''
- def show_time_picker(self):
- '''Open time picker dialog.'''
- time_dialog = MDTimePicker()
- time_dialog.open()
+ class Test(MDApp):
+ def build(self):
+ self.theme_cls.theme_style = "Dark"
+ self.theme_cls.primary_palette = "Orange"
+ return Builder.load_string(KV)
+ def show_time_picker(self):
+ '''Open time picker dialog.'''
- Test().run()
+ time_dialog = MDTimePicker()
+ time_dialog.open()
+
+
+ Test().run()
+
+ .. tab:: Declarative python style
+
+ .. code-block:: python
+
+ from kivymd.app import MDApp
+ from kivymd.uix.button import MDRaisedButton
+ from kivymd.uix.pickers import MDTimePicker
+ from kivymd.uix.screen import MDScreen
+
+
+ class Test(MDApp):
+ def build(self):
+ self.theme_cls.theme_style = "Dark"
+ self.theme_cls.primary_palette = "Orange"
+ return (
+ MDScreen(
+ MDRaisedButton(
+ text="Open time picker",
+ pos_hint={'center_x': .5, 'center_y': .5},
+ on_release=self.show_time_picker,
+ )
+ )
+ )
+
+ def show_time_picker(self, *args):
+ '''Open time picker dialog.'''
+
+ MDTimePicker().open()
+
+
+ Test().run()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDTimePicker.png
:align: center
@@ -91,14 +129,14 @@ Use the :attr:`~MDTimePicker.set_time` method of the
.. code-block:: python
- time_dialog = MDTimePicker(
- primary_color=get_color_from_hex("#72225b"),
- accent_color=get_color_from_hex("#5d1a4a"),
- text_button_color=(1, 1, 1, 1),
- )
+ MDTimePicker(
+ primary_color="brown",
+ accent_color="red",
+ text_button_color="white",
+ ).open()
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/time-picker-customization.png
- :align: center
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/time-picker-customization.png
+ :align: center
"""
__all__ = ("MDTimePicker",)
@@ -194,8 +232,8 @@ class TimeInputTextField(MDTextField):
hour_regx = "^[0-9]$|^0[1-9]$|^1[0-2]$"
minute_regx = "^[0-9]$|^0[0-9]$|^[1-5][0-9]$"
- def __init__(self, **kwargs):
- super().__init__(**kwargs)
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
Clock.schedule_once(self.set_text)
self.register_event_type("on_select")
self.bind(text_color_focus=self.setter("hint_text_color_normal"))
@@ -217,17 +255,20 @@ class TimeInputTextField(MDTextField):
to somehow make them aligned.
"""
- if not self.text:
- self.text = " "
+ def set_text(*args):
+ if not self.text:
+ self.text = " "
+
+ self._refresh_text(self.text)
+ max_size = max(self._lines_rects, key=lambda r: r.size[0]).size
+ dx = (self.width - max_size[0]) / 2.0
+ dy = (self.height - max_size[1]) / 2.0
+ self.padding = [dx, dy, dx, dy]
- self._refresh_text(self.text)
- max_size = max(self._lines_rects, key=lambda r: r.size[0]).size
- dx = (self.width - max_size[0]) / 2.0
- dy = (self.height - max_size[1]) / 2.0
- self.padding = [dx, dy, dx, dy]
+ if len(self.text) > 1:
+ self.text = self.text.replace(" ", "")
- if len(self.text) > 1:
- self.text = self.text.replace(" ", "")
+ Clock.schedule_once(set_text)
def on_focus(self, *args) -> None:
super().on_focus(*args)
diff --git a/sbapp/kivymd/uix/refreshlayout/refreshlayout.py b/sbapp/kivymd/uix/refreshlayout/refreshlayout.py
index e6aafad9..170b76e3 100755
--- a/sbapp/kivymd/uix/refreshlayout/refreshlayout.py
+++ b/sbapp/kivymd/uix/refreshlayout/refreshlayout.py
@@ -36,7 +36,7 @@ Example
title: app.title
md_bg_color: app.theme_cls.primary_color
background_palette: 'Primary'
- elevation: 10
+ elevation: 4
left_action_items: [['menu', lambda x: x]]
MDScrollViewRefreshLayout:
diff --git a/sbapp/kivymd/uix/screen.py b/sbapp/kivymd/uix/screen.py
index 609e5074..2e399ecf 100644
--- a/sbapp/kivymd/uix/screen.py
+++ b/sbapp/kivymd/uix/screen.py
@@ -29,7 +29,7 @@ MDScreen
md_bg_color: app.theme_cls.primary_color
"""
-from kivy.properties import ObjectProperty
+from kivy.properties import ListProperty, ObjectProperty
from kivy.uix.screenmanager import Screen
from kivymd.uix import MDAdaptiveWidget
@@ -44,20 +44,34 @@ class MDScreen(DeclarativeBehavior, Screen, MDAdaptiveWidget):
see in the :class:`~kivy.uix.screenmanager.Screen` class documentation.
"""
- hero_to = ObjectProperty()
+ hero_to = ObjectProperty(deprecated=True)
"""
- Must be a :class:`~kivymd.uix.hero.MDHeroTo` class.
+ Must be a :class:`~kivymd.uix.hero.MDHeroTo` class.
+
See the documentation of the
`MDHeroTo <https://kivymd.readthedocs.io/en/latest/components/hero/>`_
widget for more detailed information.
- .. versionchanged:: 1.0.0
+ .. deprecated:: 1.0.0
+ Use attr:`heroes_to` attribute instead.
:attr:`hero_to` is an :class:`~kivy.properties.ObjectProperty`
and defaults to `None`.
"""
- def on_hero_to(self, screen, widget) -> None:
+ heroes_to = ListProperty()
+ """
+ Must be a list of :class:`~kivymd.uix.hero.MDHeroTo` class.
+
+ .. versionadded:: 1.0.0
+
+ :attr:`heroes_to` is an :class:`~kivy.properties.LiatProperty`
+ and defaults to `[]`.
+ """
+
+ def on_hero_to(self, screen, widget: MDHeroTo) -> None:
+ """Called when the value of the :attr:`hero_to` attribute changes."""
+
if not isinstance(widget, MDHeroTo) or not issubclass(
widget.__class__, MDHeroTo
):
@@ -65,3 +79,4 @@ class MDScreen(DeclarativeBehavior, Screen, MDAdaptiveWidget):
f"The `{widget}` widget must be an `kivymd.uix.hero.MDHeroTo` "
f"class or inherited from this class"
)
+ self.heroes_to = [widget]
diff --git a/sbapp/kivymd/uix/screenmanager.py b/sbapp/kivymd/uix/screenmanager.py
index 6bf9554b..dcc4792f 100644
--- a/sbapp/kivymd/uix/screenmanager.py
+++ b/sbapp/kivymd/uix/screenmanager.py
@@ -8,8 +8,23 @@ Components/ScreenManager
If you want to use Hero animations you need to use
:class:`~kivymd.uix.screenmanager.MDScreenManager` not
:class:`~kivy.uix.screenmanager.ScreenManager` class.
+
+Transition
+----------
+
+:class:`~kivymd.uix.screenmanager.MDScreenManager` class supports the following
+transitions:
+
+- :class:`~kivymd.uix.transition.MDFadeSlideTransition`
+- :class:`~kivymd.uix.transition.MDSlideTransition`
+- :class:`~kivymd.uix.transition.MDSwapTransition`
+
+You need to use the :class:`~kivymd.uix.screenmanager.MDScreenManager` class
+when you want to use hero animations on your screens. If you don't need hero
+animation use the :class:`~kivy.uix.screenmanager.ScreenManager` class.
"""
+from kivy import Logger
from kivy.clock import Clock
from kivy.properties import ListProperty, StringProperty
from kivy.uix.screenmanager import ScreenManager
@@ -21,17 +36,22 @@ from kivymd.uix.hero import MDHeroFrom
class MDScreenManager(DeclarativeBehavior, ScreenManager):
"""
Screen manager. This is the main class that will control your
- :class:`~kivymd.uix.screen.MDScreen` stack and memory. For more
+ :class:`~kivymd.uix.screen.MDScreen` stack and memory.
+
+ For more
information, see in the :class:`~kivy.uix.screenmanager.ScreenManager`
class documentation.
"""
- current_hero = StringProperty(None)
+ current_hero = StringProperty(None, deprecated=True)
"""
The name of the current tag for the :class:`~kivymd.uix.hero.MDHeroFrom`
and :class:`~kivymd.uix.hero.MDHeroTo` objects that will be animated when
animating the transition between screens.
+ .. deprecated:: 1.1.0
+ Use :attr:`current_heroes` attribute instead.
+
See the `Hero <https://kivymd.readthedocs.io/en/latest/components/hero/>`_
module documentation for more information about creating and using Hero
animations.
@@ -40,6 +60,17 @@ class MDScreenManager(DeclarativeBehavior, ScreenManager):
and defaults to `None`.
"""
+ current_heroes = ListProperty()
+ """
+ A list of names (tags) of heroes that need to be animated when moving
+ to the next screen.
+
+ .. versionadded:: 1.1.0
+
+ :attr:`current_heroes` is an :class:`~kivy.properties.ListProperty`
+ and defaults to `[]`.
+ """
+
# Collection of `MDHeroFrom` objects on all screens of the current
# screen manager.
_heroes_data = ListProperty()
@@ -58,28 +89,48 @@ class MDScreenManager(DeclarativeBehavior, ScreenManager):
self.transition = MDSlideTransition()
- def get_hero_from_widget(self) -> None:
+ def get_hero_from_widget(self) -> list:
"""
- Get an :class:`~kivymd.uix.hero.MDHeroTo` object with the
- :attr:`~current_hero` tag.
+ Get a list of :class:`~kivymd.uix.hero.MDHeroFrom` objects according
+ to the tag names specified in the :attr:`~current_heroes` list.
"""
- hero_from_widget = None
+ hero_from_widget = []
- for hero_widget in self._heroes_data:
- if isinstance(hero_widget, MDHeroFrom) or issubclass(
- hero_widget.__class__, MDHeroFrom
- ):
- if hero_widget.tag == self.current_hero:
- hero_from_widget = hero_widget
- break
+ for name_hero in self.current_heroes:
+ for hero_widget in self._heroes_data:
+ if isinstance(hero_widget, MDHeroFrom) or issubclass(
+ hero_widget.__class__, MDHeroFrom
+ ):
+ if hero_widget.tag == name_hero:
+ hero_from_widget.append(hero_widget)
return hero_from_widget
+ def on_current_hero(self, instance, value: str) -> None:
+ """
+ Called when the value of the :attr:`current_hero` attribute changes.
+ """
+
+ Logger.warning(
+ "KivyMD: "
+ "`kivymd/uix/screenmanager.MDScreenManager.current_hero` "
+ "attribute is deprecated. "
+ "Use `kivymd/uix/screenmanager.MDScreenManager.current_heroes` "
+ "attribute instead."
+ )
+ if value:
+ self.current_heroes = [value]
+ else:
+ self.current_heroes = []
+
def add_widget(self, widget, *args, **kwargs):
super().add_widget(widget, *args, **kwargs)
Clock.schedule_once(lambda x: self._create_heroes_data(widget))
+ # TODO: Add a method to delete an object from the arrt:`_heroes_data`
+ # collection when deleting an object using the `remove_widget` method.
+
def _create_heroes_data(self, widget):
def find_hero_widget(child_widget):
widget_hero = None
diff --git a/sbapp/kivymd/uix/segmentedcontrol/segmentedcontrol.kv b/sbapp/kivymd/uix/segmentedcontrol/segmentedcontrol.kv
index 9aed57f8..3ac36560 100644
--- a/sbapp/kivymd/uix/segmentedcontrol/segmentedcontrol.kv
+++ b/sbapp/kivymd/uix/segmentedcontrol/segmentedcontrol.kv
@@ -15,8 +15,11 @@
pos_hint: {"center_y": .5}
x: root._segment_switch_x
md_bg_color: root.segment_color
- elevation: 6
+ elevation: 2
_radius: root.radius[0] - 4
+ width:
+ segment_panel.width / segment_panel.children_number \
+ - segment_panel.spacing
SegmentPanel:
id: segment_panel
diff --git a/sbapp/kivymd/uix/segmentedcontrol/segmentedcontrol.py b/sbapp/kivymd/uix/segmentedcontrol/segmentedcontrol.py
index 6a518c21..5e9c7f42 100644
--- a/sbapp/kivymd/uix/segmentedcontrol/segmentedcontrol.py
+++ b/sbapp/kivymd/uix/segmentedcontrol/segmentedcontrol.py
@@ -10,58 +10,77 @@ Components/SegmentedControl
Usage
=====
-.. code-block:: python
+.. tabs::
- from kivy.lang import Builder
+ .. tab:: Declarative KV style
- from kivymd.app import MDApp
+ .. code-block:: python
+ from kivy.lang import Builder
- KV = '''
- MDScreen:
+ from kivymd.app import MDApp
- MDSegmentedControl:
- pos_hint: {"center_x": .5, "center_y": .5}
- MDSegmentedControlItem:
- text: "Male"
+ KV = '''
+ MDScreen:
- MDSegmentedControlItem:
- text: "Female"
+ MDSegmentedControl:
+ pos_hint: {"center_x": .5, "center_y": .5}
- MDSegmentedControlItem:
- text: "All"
- '''
+ MDSegmentedControlItem:
+ text: "Male"
+ MDSegmentedControlItem:
+ text: "Female"
- class Test(MDApp):
- def build(self):
- return Builder.load_string(KV)
+ MDSegmentedControlItem:
+ text: "All"
+ '''
- Test().run()
+ class Example(MDApp):
+ def build(self):
+ self.theme_cls.theme_style = "Dark"
+ self.theme_cls.primary_palette = "Orange"
+ return Builder.load_string(KV)
-Or only in python code:
-.. code-block:: python
+ Example().run()
- from kivymd.app import MDApp
- from kivymd.uix.screen import MDScreen
- from kivymd.uix.segmentedcontrol import MDSegmentedControl, MDSegmentedControlItem
+ .. tab:: Declarative python style
+ .. code-block:: python
- class Test(MDApp):
- def build(self):
- screen = MDScreen()
- segment_control = MDSegmentedControl(pos_hint={"center_x": .5, "center_y": .5})
- segment_control.add_widget(MDSegmentedControlItem(text="Male"))
- segment_control.add_widget(MDSegmentedControlItem(text="Female"))
- segment_control.add_widget(MDSegmentedControlItem(text="All"))
- screen.add_widget(segment_control)
- return screen
+ from kivymd.app import MDApp
+ from kivymd.uix.screen import MDScreen
+ from kivymd.uix.segmentedcontrol import (
+ MDSegmentedControl, MDSegmentedControlItem
+ )
- Test().run()
+ class Example(MDApp):
+ def build(self):
+ self.theme_cls.theme_style = "Dark"
+ self.theme_cls.primary_palette = "Orange"
+ return (
+ MDScreen(
+ MDSegmentedControl(
+ MDSegmentedControlItem(
+ text="Male"
+ ),
+ MDSegmentedControlItem(
+ text="Female"
+ ),
+ MDSegmentedControlItem(
+ text="All"
+ ),
+ pos_hint={"center_x": 0.5, "center_y": 0.5}
+ )
+ )
+ )
+
+
+ Example().run()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-segmented-control-usage.gif
:align: center
@@ -117,12 +136,22 @@ with open(
class MDSegmentedControlItem(MDLabel):
- """Implements a label to place on the :class:`~SegmentPanel` panel."""
+ """
+ Implements a label to place on the :class:`~SegmentPanel` panel.
+
+ See :class:`~kivymd.uix.label.MDLabel` class documentation for more
+ information.
+ """
# TODO: Add an attribute for the color of the active segment label.
class MDSegmentedControl(MDRelativeLayout, ThemableBehavior):
"""
+ Implements a segmented control panel.
+
+ Relative layout class. For more information, see in the
+ :class:`~kivy.uix.relativelayout.RelativeLayout` class documentation.
+
:Events:
`on_active`
Called when the segment is activated.
@@ -135,7 +164,7 @@ class MDSegmentedControl(MDRelativeLayout, ThemableBehavior):
.. code-block:: kv
MDSegmentedControl:
- md_bg_color: "#451938"
+ md_bg_color: "brown"
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-segmented-control-md-bg-color.png
:align: center
@@ -151,8 +180,8 @@ class MDSegmentedControl(MDRelativeLayout, ThemableBehavior):
.. code-block:: kv
MDSegmentedControl:
- md_bg_color: "#451938"
- segment_color: "#e4514f"
+ md_bg_color: "brown"
+ segment_color: "red"
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-segmented-control-segment-color.png
:align: center
@@ -160,8 +189,8 @@ class MDSegmentedControl(MDRelativeLayout, ThemableBehavior):
.. code-block:: kv
MDSegmentedControl:
- md_bg_color: "#451938"
- segment_color: "#e4514f"
+ md_bg_color: "brown"
+ segment_color: "red"
MDSegmentedControlItem:
text: "[color=fff]Male[/color]"
@@ -196,9 +225,9 @@ class MDSegmentedControl(MDRelativeLayout, ThemableBehavior):
.. code-block:: kv
MDSegmentedControl:
- md_bg_color: "#451938"
- segment_color: "#e4514f"
- separator_color: 1, 1, 1, 1
+ md_bg_color: "brown"
+ segment_color: "red"
+ separator_color: "white"
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-segmented-control-separator-color.png
:align: center
@@ -255,9 +284,6 @@ class MDSegmentedControl(MDRelativeLayout, ThemableBehavior):
Clock.schedule_once(self.set_default_colors)
Clock.schedule_once(self._remove_last_separator)
- # FIXME: Sometimes this interval is not enough to get the width
- # of the segment label textures.
- Clock.schedule_once(self._set_width_segment_switch, 2.2)
def set_default_colors(self, *args) -> None:
"""
@@ -313,6 +339,10 @@ class MDSegmentedControl(MDRelativeLayout, ThemableBehavior):
self.ids.segment_panel.add_widget(widget)
separator = MDSeparator(orientation="vertical")
self.ids.segment_panel.add_widget(separator)
+ if not self.ids.segment_panel._started:
+ self.ids.segment_panel._started = True
+ else:
+ self.ids.segment_panel.children_number += 1
Clock.schedule_once(
lambda x: self.update_separator_color(separator)
)
@@ -326,15 +356,6 @@ class MDSegmentedControl(MDRelativeLayout, ThemableBehavior):
self.current_active_segment = widget
self.dispatch("on_active", widget)
- def _set_width_segment_switch(self, *args):
- """
- Sets the width of the switch. I think this is not done quite correctly.
- """
-
- self.ids.segment_switch.width = self.ids.segment_panel.children[
- 0
- ].width + dp(12)
-
def _remove_last_separator(self, *args):
self.ids.segment_panel.remove_widget(self.ids.segment_panel.children[0])
@@ -350,3 +371,7 @@ class SegmentPanel(MDBoxLayout):
Implements a panel for placing items - :class:`~MDSegmentedControlItem`
for the :class:`~MDSegmentedControl` class.
"""
+
+ children_number = NumericProperty(1)
+
+ _started = BooleanProperty(defaultvalue=False)
diff --git a/sbapp/kivymd/uix/selectioncontrol/selectioncontrol.kv b/sbapp/kivymd/uix/selectioncontrol/selectioncontrol.kv
index 1e7480af..3d3b015f 100644
--- a/sbapp/kivymd/uix/selectioncontrol/selectioncontrol.kv
+++ b/sbapp/kivymd/uix/selectioncontrol/selectioncontrol.kv
@@ -101,7 +101,7 @@
size_hint: None, None
size: dp(24), dp(24)
elevation:
- (8 if root.active else 5) \
+ (2.5 if root.active else 1) \
if app.theme_cls.material_style != "M3" else \
0
pos:
diff --git a/sbapp/kivymd/uix/selectioncontrol/selectioncontrol.py b/sbapp/kivymd/uix/selectioncontrol/selectioncontrol.py
index 48d0c9b3..031fd3a3 100755
--- a/sbapp/kivymd/uix/selectioncontrol/selectioncontrol.py
+++ b/sbapp/kivymd/uix/selectioncontrol/selectioncontrol.py
@@ -192,15 +192,10 @@ from kivy.properties import (
)
from kivy.uix.behaviors import ToggleButtonBehavior
from kivy.uix.floatlayout import FloatLayout
-from kivy.utils import get_color_from_hex
from kivymd import uix_path
-from kivymd.color_definitions import colors
from kivymd.theming import ThemableBehavior
-from kivymd.uix.behaviors import (
- CircularRippleBehavior,
- FakeCircularElevationBehavior,
-)
+from kivymd.uix.behaviors import CircularRippleBehavior, CommonElevationBehavior
from kivymd.uix.floatlayout import MDFloatLayout
from kivymd.uix.label import MDIcon
@@ -361,8 +356,10 @@ class MDCheckbox(CircularRippleBehavior, ToggleButtonBehavior, MDIcon):
disabled=self.update_color,
state=self.update_color,
)
- self.theme_cls.bind(primary_color=self.update_primary_color)
- self.theme_cls.bind(theme_style=self.update_primary_color)
+ self.theme_cls.bind(
+ theme_style=self.update_primary_color,
+ primary_color=self.update_primary_color,
+ )
self.update_icon()
self.update_color()
@@ -423,7 +420,7 @@ class ThumbIcon(MDIcon):
class Thumb(
- FakeCircularElevationBehavior,
+ CommonElevationBehavior,
CircularRippleBehavior,
MDFloatLayout,
):
diff --git a/sbapp/kivymd/uix/slider/slider.kv b/sbapp/kivymd/uix/slider/slider.kv
index 4f363447..53323811 100644
--- a/sbapp/kivymd/uix/slider/slider.kv
+++ b/sbapp/kivymd/uix/slider/slider.kv
@@ -3,7 +3,7 @@
#:import colors kivymd.color_definitions.colors
-<HintBoxContainer@MDCard+FakeRectangularElevationBehavior>
+<HintBoxContainer@MDCard>
<MDSlider>
@@ -131,16 +131,20 @@
) \
) \
)
- elevation: 0 if root._is_off else (4 if root.active else 2)
+ elevation: 0 if root._is_off else (3 if root.active else 1)
HintBoxContainer:
id: hint_box
size_hint: None, None
- md_bg_color: root.hint_bg_color
- elevation: 0
+ md_bg_color: root.hint_bg_color if root.hint_bg_color else [0, 0, 0, 0]
+ elevation: 1.5
opacity: 1 if root.active else 0
radius: root.hint_radius
padding: "6dp", "6dp", "6dp", "8dp"
+ shadow_color:
+ ([0, 0, 0, 0.6] if root.hint_bg_color else [0, 0, 0, 0]) \
+ if root.active else \
+ [0, 0, 0, 0]
size:
lbl_value.width + self.padding[0] * 2, \
lbl_value.height + self.padding[0]
diff --git a/sbapp/kivymd/uix/slider/slider.py b/sbapp/kivymd/uix/slider/slider.py
index 5925abcb..152a602a 100644
--- a/sbapp/kivymd/uix/slider/slider.py
+++ b/sbapp/kivymd/uix/slider/slider.py
@@ -82,7 +82,7 @@ class MDSlider(ThemableBehavior, Slider):
and defaults to `True`.
"""
- hint_bg_color = ColorProperty([0, 0, 0, 0])
+ hint_bg_color = ColorProperty(None)
"""
Hint rectangle color in (r.g.b.a) format.
diff --git a/sbapp/kivymd/uix/sliverappbar/sliverappbar.py b/sbapp/kivymd/uix/sliverappbar/sliverappbar.py
index 2bfb242b..6316315e 100644
--- a/sbapp/kivymd/uix/sliverappbar/sliverappbar.py
+++ b/sbapp/kivymd/uix/sliverappbar/sliverappbar.py
@@ -38,8 +38,8 @@ Example
from kivy.lang.builder import Builder
+ from kivymd.app import MDApp
from kivymd.uix.card import MDCard
- from kivymd.uix.behaviors import RoundedRectangularElevationBehavior
KV = '''
<CardItem>
@@ -47,7 +47,6 @@ Example
height: "86dp"
padding: "4dp"
radius: 12
- elevation: 4
FitImage:
source: "avatar.jpg"
@@ -95,8 +94,10 @@ Example
'''
- class CardItem(MDCard, RoundedRectangularElevationBehavior):
- pass
+ class CardItem(MDCard):
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.elevation = 3
class Example(MDApp):
@@ -192,7 +193,6 @@ class MDSliverAppbar(MDBoxLayout, ThemableBehavior):
from kivymd.uix.card import MDCard
from kivymd.uix.toolbar import MDTopAppBar
- from kivymd.uix.behaviors import RoundedRectangularElevationBehavior
KV = '''
#:import SliverToolbar __main__.SliverToolbar
@@ -203,7 +203,6 @@ class MDSliverAppbar(MDBoxLayout, ThemableBehavior):
height: "86dp"
padding: "4dp"
radius: 12
- elevation: 4
FitImage:
source: "avatar.jpg"
@@ -252,13 +251,16 @@ class MDSliverAppbar(MDBoxLayout, ThemableBehavior):
'''
- class CardItem(MDCard, RoundedRectangularElevationBehavior):
- pass
+ class CardItem(MDCard):
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.elevation = 3
class SliverToolbar(MDTopAppBar):
def __init__(self, **kwargs):
super().__init__(**kwargs)
+ self.shadow_color = (0, 0, 0, 0)
self.type_height = "medium"
self.headline_text = "Headline medium"
self.left_action_items = [["arrow-left", lambda x: x]]
@@ -422,6 +424,7 @@ class MDSliverAppbar(MDBoxLayout, ThemableBehavior):
# Adding a custom MDTopAppBar object.
if issubclass(instance_toolbar_cls.__class__, MDTopAppBar):
instance_toolbar_cls.pos_hint = {"top": 1}
+ instance_toolbar_cls.elevation = 0
self.ids.float_box.add_widget(instance_toolbar_cls)
else:
raise MDSliverAppbarException(
diff --git a/sbapp/kivymd/uix/snackbar/snackbar.kv b/sbapp/kivymd/uix/snackbar/snackbar.kv
index 4dcef8b5..557ceda7 100644
--- a/sbapp/kivymd/uix/snackbar/snackbar.kv
+++ b/sbapp/kivymd/uix/snackbar/snackbar.kv
@@ -8,7 +8,7 @@
padding: "10dp", "10dp", "10dp", "10dp"
md_bg_color: "323232" if not root.bg_color else root.bg_color
radius: root.radius
- elevation: 11 if root.padding else 0
+ elevation: 4 if root.padding else 0
canvas:
Color:
diff --git a/sbapp/kivymd/uix/snackbar/snackbar.py b/sbapp/kivymd/uix/snackbar/snackbar.py
index 6acb96cf..c4fbdc97 100755
--- a/sbapp/kivymd/uix/snackbar/snackbar.py
+++ b/sbapp/kivymd/uix/snackbar/snackbar.py
@@ -284,7 +284,6 @@ from kivy.properties import (
)
from kivymd import uix_path
-from kivymd.uix.behaviors import FakeRectangularElevationBehavior
from kivymd.uix.button import BaseButton
from kivymd.uix.card import MDCard
@@ -294,7 +293,7 @@ with open(
Builder.load_string(kv_file.read())
-class BaseSnackbar(MDCard, FakeRectangularElevationBehavior):
+class BaseSnackbar(MDCard):
"""
:Events:
:attr:`on_open`
diff --git a/sbapp/kivymd/uix/swiper/swiper.py b/sbapp/kivymd/uix/swiper/swiper.py
index bcf88d0b..b89e2e54 100644
--- a/sbapp/kivymd/uix/swiper/swiper.py
+++ b/sbapp/kivymd/uix/swiper/swiper.py
@@ -38,7 +38,7 @@ Example
MDTopAppBar:
id: toolbar
title: "MDSwiper"
- elevation: 10
+ elevation: 4
pos_hint: {"top": 1}
MDSwiper:
@@ -142,7 +142,7 @@ Example
MDTopAppBar:
id: toolbar
title: "MDSwiper"
- elevation: 10
+ elevation: 4
pos_hint: {"top": 1}
MDSwiper:
@@ -203,7 +203,6 @@ from kivy.animation import Animation
from kivy.clock import Clock
from kivy.core.window import Window
from kivy.effects.dampedscroll import DampedScrollEffect
-from kivy.event import EventDispatcher
from kivy.lang.builder import Builder
from kivy.properties import (
BooleanProperty,
@@ -212,7 +211,6 @@ from kivy.properties import (
StringProperty,
)
from kivy.uix.anchorlayout import AnchorLayout
-from kivy.uix.boxlayout import BoxLayout
from kivy.utils import platform
from kivymd import uix_path
@@ -294,7 +292,7 @@ class MDSwiperItem(MDBoxLayout):
anim.start(self)
-class MDSwiper(MDScrollView, EventDispatcher):
+class MDSwiper(MDScrollView):
items_spacing = NumericProperty("20dp")
"""
The space between each :class:`MDSwiperItem`.
diff --git a/sbapp/kivymd/uix/tab/tab.kv b/sbapp/kivymd/uix/tab/tab.kv
index 824f82e3..f219bc1e 100644
--- a/sbapp/kivymd/uix/tab/tab.kv
+++ b/sbapp/kivymd/uix/tab/tab.kv
@@ -79,6 +79,10 @@
layout: layout
size_hint: 1, None
elevation: root.elevation
+ radius: root.radius
+ shadow_offset: root.shadow_offset
+ shadow_color: root.shadow_color
+ shadow_softness: root.shadow_softness
height: root.tab_bar_height
md_bg_color:
self.theme_cls.primary_color \
diff --git a/sbapp/kivymd/uix/tab/tab.py b/sbapp/kivymd/uix/tab/tab.py
index 6c93dbfd..816711e9 100755
--- a/sbapp/kivymd/uix/tab/tab.py
+++ b/sbapp/kivymd/uix/tab/tab.py
@@ -944,7 +944,6 @@ from kivy.properties import (
from kivy.uix.anchorlayout import AnchorLayout
from kivy.uix.behaviors import ToggleButtonBehavior
from kivy.uix.scrollview import ScrollView
-from kivy.uix.widget import Widget
from kivy.utils import boundary
from kivymd import uix_path
@@ -953,11 +952,11 @@ from kivymd.icon_definitions import md_icons
from kivymd.theming import ThemableBehavior, ThemeManager
from kivymd.uix.behaviors import (
DeclarativeBehavior,
- FakeRectangularElevationBehavior,
RectangularRippleBehavior,
SpecificBackgroundColorBehavior,
)
from kivymd.uix.boxlayout import MDBoxLayout
+from kivymd.uix.card import MDCard
from kivymd.uix.carousel import MDCarousel
from kivymd.uix.label import MDLabel
@@ -1024,7 +1023,7 @@ class MDTabsLabel(ToggleButtonBehavior, RectangularRippleBehavior, MDLabel):
Clock.schedule_once(self.tab_bar._label_request_indicator_update, 0)
-class MDTabsBase(Widget):
+class MDTabsBase:
"""
This class allow you to create a tab.
You must create a new class that inherits from MDTabsBase.
@@ -1130,9 +1129,9 @@ class MDTabsBase(Widget):
This property will affect the Tab's Title Label widget.
"""
- def __init__(self, **kwargs):
+ def __init__(self, *args, **kwargs):
self.tab_label = MDTabsLabel(tab=self)
- super().__init__(**kwargs)
+ super().__init__(*args, **kwargs)
self.bind(
icon=self._update_text,
title=self._update_text,
@@ -1273,9 +1272,7 @@ class MDTabsScrollView(ScrollView):
_update(self.effect_y, scroll_y)
-class MDTabsBar(
- ThemableBehavior, FakeRectangularElevationBehavior, MDBoxLayout
-):
+class MDTabsBar(MDCard):
"""
This class is just a boxlayout that contains the scroll view for tabs.
It is also responsible for resizing the tab shortcut when necessary.
@@ -1551,13 +1548,43 @@ class MDTabs(
and defaults to `None`.
"""
- elevation = NumericProperty(0)
+ shadow_softness = NumericProperty(12)
+ """
+ See :attr:`kivymd.uix.behaviors.CommonElevationBehavior.shadow_softness`
+ attribute.
+
+ .. versionadded:: 1.1.0
+
+ :attr:`shadow_softness` is an :class:`~kivy.properties.NumericProperty`
+ and defaults to `12`.
+ """
+
+ shadow_color = ColorProperty([0, 0, 0, 0.6])
+ """
+ See :attr:`kivymd.uix.behaviors.CommonElevationBehavior.shadow_color`
+ attribute.
+
+ .. versionadded:: 1.1.0
+
+ :attr:`shadow_color` is an :class:`~kivy.properties.ColorProperty`
+ and defaults to `[0, 0, 0, 0.6]`.
+ """
+
+ shadow_offset = ListProperty((0, 0))
"""
- Tab value elevation.
+ See :attr:`kivymd.uix.behaviors.CommonElevationBehavior.shadow_offset`
+ attribute.
+
+ .. versionadded:: 1.1.0
- .. seealso::
+ :attr:`shadow_offset` is an :class:`~kivy.properties.ListProperty`
+ and defaults to `[0, 0]`.
+ """
- `Behaviors/Elevation <https://kivymd.readthedocs.io/en/latest/behaviors/elevation/index.html>`_
+ elevation = NumericProperty(0)
+ """
+ See :attr:`kivymd.uix.behaviors.CommonElevationBehavior.elevation`
+ attribute.
:attr:`elevation` is an :class:`~kivy.properties.NumericProperty`
and defaults to `0`.
diff --git a/sbapp/kivymd/uix/taptargetview.py b/sbapp/kivymd/uix/taptargetview.py
index 1e57ff09..735de0d0 100644
--- a/sbapp/kivymd/uix/taptargetview.py
+++ b/sbapp/kivymd/uix/taptargetview.py
@@ -487,6 +487,8 @@ class MDTapTargetView(ThemableBehavior, EventDispatcher):
_outer_radius = NumericProperty(0)
_target_radius = NumericProperty(0)
+ __elevation = 0
+
def __init__(self, **kwargs):
self.ripple_max_dist = dp(90)
self.on_outer_radius(self, self.outer_radius)
@@ -514,6 +516,89 @@ class MDTapTargetView(ThemableBehavior, EventDispatcher):
if not self.outer_circle_color:
self.outer_circle_color = self.theme_cls.primary_color[:-1]
+ def start(self, *args):
+ """Starts widget opening animation."""
+
+ self._initialize()
+ self._animate_outer()
+ self.state = "open"
+ self.core_title_text.opacity = 1
+ self.core_description_text.opacity = 1
+ self.dispatch("on_open")
+
+ elevation = getattr(self.widget, "elevation", None)
+ if elevation:
+ self.__elevation = elevation
+ self.widget.elevation = 0
+
+ def stop(self, *args):
+ """Starts widget close animation."""
+
+ # It needs a better implementation.
+ if self.anim_ripple is not None:
+ self.anim_ripple.unbind(on_complete=self._repeat_ripple)
+ self.core_title_text.opacity = 0
+ self.core_description_text.opacity = 0
+ anim = Animation(
+ d=0.15,
+ t="in_cubic",
+ **dict(
+ zip(
+ ["_outer_radius", "_target_radius", "target_ripple_radius"],
+ [0, 0, 0],
+ )
+ ),
+ )
+ anim.bind(on_complete=self._after_stop)
+ anim.start(self.widget)
+
+ def on_open(self, *args):
+ """Called at the time of the start of the widget opening animation."""
+
+ def on_close(self, *args):
+ """Called at the time of the start of the widget closed animation."""
+
+ def on_draw_shadow(self, instance, value):
+ Logger.warning(
+ "The shadow adding method will be implemented in future versions"
+ )
+
+ def on_description_text(self, instance, value):
+ self.core_description_text.text = value
+
+ def on_description_text_size(self, instance, value):
+ self.core_description_text.font_size = value
+
+ def on_description_text_bold(self, instance, value):
+ self.core_description_text.bold = value
+
+ def on_title_text(self, instance, value):
+ self.core_title_text.text = value
+
+ def on_title_text_size(self, instance, value):
+ self.core_title_text.font_size = value
+
+ def on_title_text_bold(self, instance, value):
+ self.core_title_text.bold = value
+
+ def on_outer_radius(self, instance, value):
+ self._outer_radius = self.outer_radius * 2
+
+ def on_target_radius(self, instance, value):
+ self._target_radius = self.target_radius * 2
+
+ def on_target_touch(self):
+ if self.stop_on_target_touch:
+ self.stop()
+
+ def on_outer_touch(self):
+ if self.stop_on_outer_touch:
+ self.stop()
+
+ def on_outside_click(self):
+ if self.cancelable:
+ self.stop()
+
def _initialize(self):
setattr(self.widget, "_outer_radius", 0)
setattr(self.widget, "_target_radius", 0)
@@ -527,7 +612,7 @@ class MDTapTargetView(ThemableBehavior, EventDispatcher):
def _draw_canvas(self):
_pos = self._ttv_pos()
- self.widget.canvas.before.clear()
+ self.widget.canvas.before.remove_group("ttv_group")
with self.widget.canvas.before:
# Outer circle.
@@ -588,34 +673,14 @@ class MDTapTargetView(ThemableBehavior, EventDispatcher):
group="ttv_group",
)
- def stop(self, *args):
- """Starts widget close animation."""
-
- # It needs a better implementation.
- if self.anim_ripple is not None:
- self.anim_ripple.unbind(on_complete=self._repeat_ripple)
- self.core_title_text.opacity = 0
- self.core_description_text.opacity = 0
- anim = Animation(
- d=0.15,
- t="in_cubic",
- **dict(
- zip(
- ["_outer_radius", "_target_radius", "target_ripple_radius"],
- [0, 0, 0],
- )
- ),
- )
- anim.bind(on_complete=self._after_stop)
- anim.start(self.widget)
-
def _after_stop(self, *args):
self.widget.canvas.before.remove_group("ttv_group")
args[0].stop_all(self.widget)
- elev = getattr(self.widget, "elevation", None)
- if elev:
- self._fix_elev()
+ elevation = getattr(self.widget, "elevation", None)
+ if elevation:
+ self.widget.elevation = self.__elevation
+
self.dispatch("on_close")
# Don't forget to unbind the function or it'll mess
@@ -639,16 +704,6 @@ class MDTapTargetView(ThemableBehavior, EventDispatcher):
)
Color(a=1)
- def start(self, *args):
- """Starts widget opening animation."""
-
- self._initialize()
- self._animate_outer()
- self.state = "open"
- self.core_title_text.opacity = 1
- self.core_description_text.opacity = 1
- self.dispatch("on_open")
-
def _animate_outer(self):
anim = Animation(
d=0.2,
@@ -684,53 +739,6 @@ class MDTapTargetView(ThemableBehavior, EventDispatcher):
setattr(self.widget, "target_ripple_alpha", 1)
self._animate_ripple()
- def on_open(self, *args):
- """Called at the time of the start of the widget opening animation."""
-
- def on_close(self, *args):
- """Called at the time of the start of the widget closed animation."""
-
- def on_draw_shadow(self, instance, value):
- Logger.warning(
- "The shadow adding method will be implemented in future versions"
- )
-
- def on_description_text(self, instance, value):
- self.core_description_text.text = value
-
- def on_description_text_size(self, instance, value):
- self.core_description_text.font_size = value
-
- def on_description_text_bold(self, instance, value):
- self.core_description_text.bold = value
-
- def on_title_text(self, instance, value):
- self.core_title_text.text = value
-
- def on_title_text_size(self, instance, value):
- self.core_title_text.font_size = value
-
- def on_title_text_bold(self, instance, value):
- self.core_title_text.bold = value
-
- def on_outer_radius(self, instance, value):
- self._outer_radius = self.outer_radius * 2
-
- def on_target_radius(self, instance, value):
- self._target_radius = self.target_radius * 2
-
- def on_target_touch(self):
- if self.stop_on_target_touch:
- self.stop()
-
- def on_outer_touch(self):
- if self.stop_on_outer_touch:
- self.stop()
-
- def on_outside_click(self):
- if self.cancelable:
- self.stop()
-
def _some_func(self, wid, touch):
"""
This function decides which one to dispatch based on the touch
diff --git a/sbapp/kivymd/uix/templates/rotatewidget/rotatewidget.kv b/sbapp/kivymd/uix/templates/rotatewidget/rotatewidget.kv
deleted file mode 100644
index 77b57df3..00000000
--- a/sbapp/kivymd/uix/templates/rotatewidget/rotatewidget.kv
+++ /dev/null
@@ -1,9 +0,0 @@
-<RotateWidget>
- canvas.before:
- PushMatrix
- Rotate:
- angle: self.rotate_value_angle
- axis: tuple(self.rotate_value_axis)
- origin: self.center
- canvas.after:
- PopMatrix
diff --git a/sbapp/kivymd/uix/templates/rotatewidget/rotatewidget.py b/sbapp/kivymd/uix/templates/rotatewidget/rotatewidget.py
index d3491df0..9416a5f3 100644
--- a/sbapp/kivymd/uix/templates/rotatewidget/rotatewidget.py
+++ b/sbapp/kivymd/uix/templates/rotatewidget/rotatewidget.py
@@ -2,127 +2,31 @@
Templates/RotateWidget
======================
-.. versionadded:: 1.0.0
+.. deprecated:: 1.0.0
-Base class for controlling the rotate of the widget.
-
-.. note:: See `kivy.graphics.Rotate
- <https://kivy.org/doc/stable/api-kivy.graphics.html#kivy.graphics.Rotate>`_
- for more information.
-
-Kivy
-----
-
-.. code-block:: python
-
- from kivy.animation import Animation
- from kivy.lang import Builder
- from kivy.app import App
- from kivy.properties import NumericProperty
- from kivy.uix.button import Button
-
- KV = '''
- Screen:
-
- RotateButton:
- size_hint: .5, .5
- pos_hint: {"center_x": .5, "center_y": .5}
- on_release: app.change_rotate(self)
-
- canvas.before:
- PushMatrix
- Rotate:
- angle: self.rotate_value_angle
- axis: 0, 0, 1
- origin: self.center
- canvas.after:
- PopMatrix
- '''
-
-
- class RotateButton(Button):
- rotate_value_angle = NumericProperty(0)
-
-
- class Test(App):
- def build(self):
- return Builder.load_string(KV)
-
- def change_rotate(self, instance_button: Button) -> None:
- Animation(rotate_value_angle=45, d=0.3).start(instance_button)
-
-
- Test().run()
-
-KivyMD
-------
-
-.. code-block:: python
-
- from kivy.animation import Animation
- from kivy.lang import Builder
-
- from kivymd.app import MDApp
- from kivymd.uix.button import MDRaisedButton
- from kivymd.uix.templates import RotateWidget
-
- KV = '''
- MDScreen:
-
- RotateButton:
- size_hint: .5, .5
- pos_hint: {"center_x": .5, "center_y": .5}
- on_release: app.change_rotate(self)
- elevation:0
- '''
-
-
- class RotateButton(MDRaisedButton, RotateWidget):
- pass
-
-
- class Test(MDApp):
- def build(self):
- return Builder.load_string(KV)
-
- def change_rotate(self, instance_button: MDRaisedButton) -> None:
- Animation(rotate_value_angle=45, d=0.3).start(instance_button)
-
-
- Test().run()
+.. note:: `RotateWidget` class has been deprecated. Please use
+ `RotateBahavior <https://kivymd.readthedocs.io/en/latest/behaviors/rotate/>`_
+ class instead.
"""
__all__ = ("RotateWidget",)
-import os
-
-from kivy.lang import Builder
-from kivy.properties import ListProperty, NumericProperty
-
-from kivymd import uix_path
-
-with open(
- os.path.join(uix_path, "templates", "rotatewidget", "rotatewidget.kv"),
- encoding="utf-8",
-) as kv_file:
- Builder.load_string(kv_file.read())
+from kivy import Logger
+from kivymd.uix.behaviors import RotateBehavior
-class RotateWidget:
- """Base class for controlling the rotate of the widget."""
- rotate_value_angle = NumericProperty(0)
+class RotateWidget(RotateBehavior):
"""
- Property for getting/setting the angle of the rotation.
-
- :attr:`rotate_value_angle` is an :class:`~kivy.properties.NumericProperty`
- and defaults to `0`.
- """
-
- rotate_value_axis = ListProperty((0, 0, 1))
+ .. deprecated:: 1.1.0
+ Use :class:`~kivymd.uix.behaviors.rotate_behavior.RotateBehavior`
+ class instead.
"""
- Property for getting/setting the axis of the rotation.
- :attr:`rotate_value_axis` is an :class:`~kivy.properties.NumericProperty`
- and defaults to `(0, 0, 1)`.
- """
+ def __init__(self, **kwargs):
+ super().__init__(**kwargs)
+ Logger.warning(
+ "KivyMD: "
+ "The `RotateWidget` class has been deprecated. "
+ "Use the `RotateBehavior` class instead."
+ )
diff --git a/sbapp/kivymd/uix/templates/scalewidget/scalewidget.kv b/sbapp/kivymd/uix/templates/scalewidget/scalewidget.kv
deleted file mode 100644
index 5fe002f7..00000000
--- a/sbapp/kivymd/uix/templates/scalewidget/scalewidget.kv
+++ /dev/null
@@ -1,10 +0,0 @@
-<ScaleWidget>
- canvas.before:
- PushMatrix
- Scale:
- x: self.scale_value_x
- y: self.scale_value_y
- z: self.scale_value_x
- origin: self.center
- canvas.after:
- PopMatrix
diff --git a/sbapp/kivymd/uix/templates/scalewidget/scalewidget.py b/sbapp/kivymd/uix/templates/scalewidget/scalewidget.py
index 9f9204fe..3fe69850 100644
--- a/sbapp/kivymd/uix/templates/scalewidget/scalewidget.py
+++ b/sbapp/kivymd/uix/templates/scalewidget/scalewidget.py
@@ -2,149 +2,33 @@
Templates/ScaleWidget
=====================
-.. versionadded:: 1.0.0
+.. deprecated:: 1.1.0
Base class for controlling the scale of the widget.
-.. note:: See `kivy.graphics.Scale
- <https://kivy.org/doc/stable/api-kivy.graphics.html#kivy.graphics.Scale>`_
- for more information.
-
-Kivy
-----
-
-.. code-block:: python
-
- from kivy.animation import Animation
- from kivy.lang import Builder
- from kivy.properties import NumericProperty
- from kivy.uix.button import Button
- from kivy.app import App
-
-
- KV = '''
- Screen:
-
- ScaleButton:
- size_hint: .5, .5
- pos_hint: {"center_x": .5, "center_y": .5}
- on_release: app.change_scale(self)
-
- canvas.before:
- PushMatrix
- Scale:
- x: self.scale_value_x
- y: self.scale_value_y
- z: self.scale_value_x
- origin: self.center
- canvas.after:
- PopMatrix
- '''
-
-
- class ScaleButton(Button):
- scale_value_x = NumericProperty(1)
- scale_value_y = NumericProperty(1)
- scale_value_z = NumericProperty(1)
-
-
- class Test(App):
- def build(self):
- return Builder.load_string(KV)
-
- def change_scale(self, instance_button: Button) -> None:
- Animation(
- scale_value_x=0.5,
- scale_value_y=0.5,
- scale_value_z=0.5,
- d=0.3,
- ).start(instance_button)
-
-
- Test().run()
-
-KivyMD
-------
-
-.. code-block:: python
-
- from kivy.animation import Animation
- from kivy.lang import Builder
-
- from kivymd.app import MDApp
- from kivymd.uix.button import MDRaisedButton
- from kivymd.uix.templates import ScaleWidget
-
- KV = '''
- MDScreen:
-
- ScaleButton:
- size_hint: .5, .5
- pos_hint: {"center_x": .5, "center_y": .5}
- on_release: app.change_scale(self)
- elevation:0
- '''
-
-
- class ScaleButton(MDRaisedButton, ScaleWidget):
- pass
-
-
- class Test(MDApp):
- def build(self):
- return Builder.load_string(KV)
-
- def change_scale(self, instance_button: MDRaisedButton) -> None:
- Animation(
- scale_value_x=0.5,
- scale_value_y=0.5,
- scale_value_z=0.5,
- d=0.3,
- ).start(instance_button)
-
-
- Test().run()
+.. note:: `ScaleWidget` class has been deprecated. Please use
+ `ScaleBehavior <https://kivymd.readthedocs.io/en/latest/behaviors/scale/>`_
+ class instead.
"""
__all__ = ("ScaleWidget",)
-import os
-
-from kivy.lang import Builder
-from kivy.properties import NumericProperty
-
-from kivymd import uix_path
+from kivy import Logger
-with open(
- os.path.join(uix_path, "templates", "scalewidget", "scalewidget.kv"),
- encoding="utf-8",
-) as kv_file:
- Builder.load_string(kv_file.read())
+from kivymd.uix.behaviors import ScaleBehavior
-class ScaleWidget:
- """Base class for controlling the scale of the widget."""
-
- scale_value_x = NumericProperty(1)
+class ScaleWidget(ScaleBehavior):
"""
- X-axis value.
-
- :attr:`scale_value_x` is an :class:`~kivy.properties.NumericProperty`
- and defaults to `1`.
+ .. deprecated:: 1.1.0
+ Use :class:`~kivymd.uix.behaviors.scale_behavior.ScaleBehavior`
+ class instead.
"""
- scale_value_y = NumericProperty(1)
- """
- Y-axis value.
-
- :attr:`scale_value_y` is an :class:`~kivy.properties.NumericProperty`
- and defaults to `1`.
- """
-
- scale_value_z = NumericProperty(1)
- """
- Z-axis value.
-
- :attr:`scale_value_z` is an :class:`~kivy.properties.NumericProperty`
- and defaults to `1`.
- """
+ def __init__(self, **kwargs):
+ super().__init__(**kwargs)
+ Logger.warning(
+ "KivyMD: "
+ "The `ScaleWidget` class has been deprecated. "
+ "Use the `ScaleBehavior` class instead."
+ )
diff --git a/sbapp/kivymd/uix/templates/stencilwidget/stencilwidget.kv b/sbapp/kivymd/uix/templates/stencilwidget/stencilwidget.kv
deleted file mode 100644
index 04807dc6..00000000
--- a/sbapp/kivymd/uix/templates/stencilwidget/stencilwidget.kv
+++ /dev/null
@@ -1,19 +0,0 @@
-<StencilWidget>
- canvas.before:
- StencilPush
- RoundedRectangle:
- pos: root.pos
- size: root.size
- # FIXME: Sometimes the radius has the value [], which get a
- # `GraphicException: Invalid radius value, must be list of tuples/numerics` error
- radius: root.radius if root.radius else [0, 0, 0, 0]
- StencilUse
- canvas.after:
- StencilUnUse
- RoundedRectangle:
- pos: root.pos
- size: root.size
- # FIXME: Sometimes the radius has the value [], which get a
- # `GraphicException: Invalid radius value, must be list of tuples/numerics` error
- radius: root.radius if root.radius else [0, 0, 0, 0]
- StencilPop
diff --git a/sbapp/kivymd/uix/templates/stencilwidget/stencilwidget.py b/sbapp/kivymd/uix/templates/stencilwidget/stencilwidget.py
index 3007857c..a2315888 100644
--- a/sbapp/kivymd/uix/templates/stencilwidget/stencilwidget.py
+++ b/sbapp/kivymd/uix/templates/stencilwidget/stencilwidget.py
@@ -2,115 +2,33 @@
Templates/StencilWidget
=======================
-.. versionadded:: 1.0.0
+.. deprecated:: 1.1.0
Base class for controlling the stencil instructions of the widget.
-.. note:: See `Stencil instructions
- <https://kivy.org/doc/stable/api-kivy.graphics.stencil_instructions.html>`_
- for more information.
-
-Kivy
-----
-
-.. code-block:: python
-
- from kivy.lang import Builder
- from kivy.app import App
-
- KV = '''
- Carousel:
-
- Button:
- size_hint: .9, .8
- pos_hint: {"center_x": .5, "center_y": .5}
-
- canvas.before:
- StencilPush
- RoundedRectangle:
- pos: root.pos
- size: root.size
- StencilUse
- canvas.after:
- StencilUnUse
- RoundedRectangle:
- pos: root.pos
- size: root.size
- StencilPop
- '''
-
-
- class Test(App):
- def build(self):
- return Builder.load_string(KV)
-
-
- Test().run()
-
-KivyMD
-------
-
-.. code-block:: python
-
- from kivy.lang import Builder
-
- from kivymd.app import MDApp
- from kivymd.uix.templates import StencilWidget
- from kivymd.uix.fitimage import FitImage
-
- KV = '''
- MDCarousel:
-
- StencilImage:
- size_hint: .9, .8
- pos_hint: {"center_x": .5, "center_y": .5}
- source: "image.png"
- '''
-
-
- class StencilImage(FitImage, StencilWidget):
- pass
-
-
- class Test(MDApp):
- def build(self):
- return Builder.load_string(KV)
-
-
- Test().run()
+.. note:: `StencilWidget` class has been deprecated. Please use
+ `StencilBehavior <https://kivymd.readthedocs.io/en/latest/behaviors/stencil/>`_
+ class instead.
"""
__all__ = ("StencilWidget",)
-import os
-
-from kivy.lang import Builder
-from kivy.properties import VariableListProperty
+from kivy import Logger
-from kivymd import uix_path
+from kivymd.uix.behaviors import StencilBehavior
-with open(
- os.path.join(uix_path, "templates", "stencilwidget", "stencilwidget.kv"),
- encoding="utf-8",
-) as kv_file:
- Builder.load_string(kv_file.read())
-
-class StencilWidget:
- """Base class for controlling the stencil instructions of the widget"""
-
- radius = VariableListProperty([0], length=4)
+class StencilWidget(StencilBehavior):
"""
- Canvas radius.
-
- .. versionadded:: 1.0.0
-
- .. code-block:: python
-
- # Top left corner slice.
- MDWidget:
- radius: [25, 0, 0, 0]
-
- :attr:`radius` is an :class:`~kivy.properties.VariableListProperty`
- and defaults to `[0, 0, 0, 0]`.
+ .. deprecated:: 1.1.0
+ Use :class:`~kivymd.uix.behaviors.scale_behavior.StencilBehavior`
+ class instead.
"""
+
+ def __init__(self, **kwargs):
+ super().__init__(**kwargs)
+ Logger.warning(
+ "KivyMD: "
+ "The `StencilWidget` class has been deprecated. "
+ "Use the `StencilBehavior` class instead."
+ )
diff --git a/sbapp/kivymd/uix/textfield/textfield.kv b/sbapp/kivymd/uix/textfield/textfield.kv
index d1b5fc3b..6f11abb1 100644
--- a/sbapp/kivymd/uix/textfield/textfield.kv
+++ b/sbapp/kivymd/uix/textfield/textfield.kv
@@ -1,4 +1,7 @@
<MDTextField>
+ input_filter: self.field_filter
+ do_backspace: self.do_backspace
+
canvas.before:
Clear
diff --git a/sbapp/kivymd/uix/textfield/textfield.py b/sbapp/kivymd/uix/textfield/textfield.py
index f68bb90b..08ea932e 100755
--- a/sbapp/kivymd/uix/textfield/textfield.py
+++ b/sbapp/kivymd/uix/textfield/textfield.py
@@ -14,7 +14,6 @@ Components/TextField
`KivyMD` provides the following field classes for use:
- MDTextField_
-- MDTextFieldRound_
- MDTextFieldRect_
.. Note:: :class:`~MDTextField` inherited from
@@ -79,15 +78,15 @@ parameter to `True`:
from kivymd.app import MDApp
KV = '''
- BoxLayout:
- padding: "10dp"
+ MDScreen:
MDTextField:
id: text_field_error
hint_text: "Helper text on error (press 'Enter')"
helper_text: "There will always be a mistake"
helper_text_mode: "on_error"
- pos_hint: {"center_y": .5}
+ pos_hint: {"center_x": .5, "center_y": .5}
+ size_hint_x: .5
'''
@@ -97,6 +96,8 @@ parameter to `True`:
self.screen = Builder.load_string(KV)
def build(self):
+ self.theme_cls.theme_style = "Dark"
+ self.theme_cls.primary_palette = "Orange"
self.screen.ids.text_field_error.bind(
on_text_validate=self.set_error_message,
on_focus=self.set_error_message,
@@ -119,6 +120,7 @@ Helper text mode `'on_error'` (with required)
MDTextField:
hint_text: "required = True"
+ text: "required = True"
required: True
helper_text_mode: "on_error"
helper_text: "Enter text"
@@ -186,7 +188,7 @@ Round mode
max_text_length: 15
helper_text: "Massage"
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-round-mode.png
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-round-mode.gif
:align: center
.. MDTextFieldRect:
@@ -203,6 +205,7 @@ MDTextFieldRect
MDTextFieldRect:
size_hint: 1, None
height: "30dp"
+ background_color: app.theme_cls.bg_normal
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-rect.gif
:align: center
@@ -278,18 +281,17 @@ __all__ = ("MDTextField", "MDTextFieldRect")
import os
import re
+from datetime import date
from typing import Union
from kivy.animation import Animation
from kivy.clock import Clock
from kivy.lang import Builder
-from kivy.logger import Logger
from kivy.metrics import dp, sp
from kivy.properties import (
AliasProperty,
BooleanProperty,
ColorProperty,
- DictProperty,
ListProperty,
NumericProperty,
ObjectProperty,
@@ -311,6 +313,220 @@ with open(
Builder.load_string(kv_file.read())
+# TODO: Add a class to work with the phone number mask.
+
+
+class AutoFormatTelephoneNumber:
+ """
+ Implements automatic formatting of the text entered in the text field
+ according to the mask, for example '+38 (###) ### ## ##'.
+ """
+
+ def __init__(self):
+ self._backspace = False
+
+ def isnumeric(self, value):
+ try:
+ int(value)
+ return True
+ except ValueError:
+ return False
+
+ def do_backspace(self, *args):
+ if self.validator and self.validator == "phone":
+ self._backspace = True
+ text = self.text
+ text = text[:-1]
+ self.text = text
+ self._backspace = False
+
+ def field_filter(self, value, boolean):
+ if self.validator and self.validator == "phone":
+ if len(self.text) == 14:
+ return
+ if self.isnumeric(value):
+ return value
+ return value
+
+ def format(self, value):
+ if value != "" and not value.isspace() and not self._backspace:
+ if len(value) <= 1 and self.focus:
+ self.text = value
+ self._check_cursor()
+ elif len(value) == 4:
+ start = self.text[:-1]
+ end = self.text[-1]
+ self.text = "%s) %s" % (start, end)
+ self._check_cursor()
+ elif len(value) == 8:
+ self.text += "-"
+ self._check_cursor()
+ elif len(value) in [12, 16]:
+ start = self.text[:-1]
+ end = self.text[-1]
+ self.text = "%s-%s" % (start, end)
+ self._check_cursor()
+
+ def _check_cursor(self):
+ def set_pos_cursor(pos_corsor, interval=0.5):
+ self.cursor = (pos_corsor, 0)
+
+ if self.focus:
+ Clock.schedule_once(lambda x: set_pos_cursor(len(self.text)), 0.1)
+
+
+class Validator:
+ """Container class for various validation methods."""
+
+ datetime_date = ObjectProperty()
+ """
+ The last valid date as a <class 'datetime.date'> object.
+
+ :attr:`datetime_date` is an :class:`~kivy.properties.ObjectProperty`
+ and defaults to `None`.
+ """
+
+ date_interval = ListProperty([None, None])
+ """
+ The date interval that is valid for input.
+ Can be entered as <class 'datetime.date'> objects or a string format.
+ Both values or just one value can be entered.
+
+ In string format, must follow the current date_format.
+ Example: Given date_format -> "mm/dd/yyyy"
+ Input examples -> "12/31/1900", "12/31/2100" or "12/31/1900", None.
+
+ :attr:`date_interval` is an :class:`~kivy.properties.ListProperty`
+ and defaults to `[None, None]`.
+ """
+
+ date_format = OptionProperty(
+ None,
+ options=[
+ "dd/mm/yyyy",
+ "mm/dd/yyyy",
+ "yyyy/mm/dd",
+ ],
+ )
+
+ """
+ Format of date strings that will be entered.
+ Available options are: `'dd/mm/yyyy'`, `'mm/dd/yyyy'`, `'yyyy/mm/dd'`.
+
+ :attr:`date_format` is an :class:`~kivy.properties.OptionProperty`
+ and defaults to `None`.
+ """
+
+ def is_email_valid(self, text: str) -> bool:
+ if not re.match(r"[^@]+@[^@]+\.[^@]+", text):
+ return True
+ return False
+
+ def is_time_valid(self, text: str) -> bool:
+ if re.match(r"^(2[0-3]|[01]?[0-9]):([0-5]?[0-9])$", text) or re.match(
+ r"^(2[0-3]|[01]?[0-9]):([0-5]?[0-9]):([0-5]?[0-9])$", text
+ ):
+ return False
+
+ return True
+
+ def is_date_valid(self, text: str) -> bool:
+ if not self.date_format:
+ raise Exception("TextInput date_format was not defined.")
+
+ # Regex strings.
+ dd = "[0][1-9]|[1-2][0-9]|[3][0-1]"
+ mm = "[0][1-9]|[1][0-2]"
+ yyyy = "[0-9][0-9][0-9][0-9]"
+ fmt = self.date_format.split("/")
+ largs = locals()
+ # Access the local variables dict in the correct format based on
+ # date_format split. Example: "mm/dd/yyyy" -> ["mm", "dd", "yyyy"]
+ # largs[fmt[0]] would be largs["mm"] so the month regex string.
+ if re.match(
+ f"^({largs[fmt[0]]})/({largs[fmt[1]]})/({largs[fmt[2]]})$", text
+ ):
+ input_split = text.split("/")
+ largs[fmt[0]] = input_split[0]
+ largs[fmt[1]] = input_split[1]
+ largs[fmt[2]] = input_split[2]
+ # Organize input into correct slots and try to convert
+ # to datetime object. This way February exceptions are
+ # tested. Also tests with the date_interval are simpler
+ # using datetime objects.
+ try:
+ datetime = date(
+ int(largs["yyyy"]), int(largs["mm"]), int(largs["dd"])
+ )
+ except ValueError:
+ return True
+
+ if self.date_interval:
+ if (
+ self.date_interval[0]
+ and not self.date_interval[0] <= datetime
+ or self.date_interval[1]
+ and not datetime <= self.date_interval[1]
+ ):
+ return True
+
+ self.datetime_date = datetime
+ return False
+ return True
+
+ def on_date_interval(self, *args) -> None:
+ """Default event handler for date_interval input."""
+
+ def on_date_interval():
+ if not self.date_format:
+ raise Exception("TextInput date_format was not defined.")
+
+ fmt = self.date_format.split("/")
+ largs = {}
+ # Convert string inputs into datetime.date objects and store
+ # them back into self.date_interval.
+ try:
+ if self.date_interval[0] and not isinstance(
+ self.date_interval[0], date
+ ):
+ split = self.date_interval[0].split("/")
+ largs[fmt[0]] = split[0]
+ largs[fmt[1]] = split[1]
+ largs[fmt[2]] = split[2]
+ self.date_interval[0] = date(
+ int(largs["yyyy"]), int(largs["mm"]), int(largs["dd"])
+ )
+ if self.date_interval[1] and not isinstance(
+ self.date_interval[1], date
+ ):
+ split = self.date_interval[1].split("/")
+ largs[fmt[0]] = split[0]
+ largs[fmt[1]] = split[1]
+ largs[fmt[2]] = split[2]
+ self.date_interval[1] = date(
+ int(largs["yyyy"]), int(largs["mm"]), int(largs["dd"])
+ )
+
+ except Exception:
+ raise Exception(
+ r"TextInput date_interval was defined incorrectly, it must "
+ r"be composed of <class 'datetime.date'> objects or strings"
+ r" following current date_format."
+ )
+
+ # Test if the interval is valid.
+ if isinstance(self.date_interval[0], date) and isinstance(
+ self.date_interval[1], date
+ ):
+ if self.date_interval[0] >= self.date_interval[1]:
+ raise Exception(
+ "TextInput date_interval last date must be greater"
+ " than the first date or set to None."
+ )
+
+ Clock.schedule_once(lambda x: on_date_interval())
+
+
class MDTextFieldRect(ThemableBehavior, TextInput):
line_anim = BooleanProperty(True)
"""
@@ -383,7 +599,13 @@ class TextfieldLabel(ThemableBehavior, Label):
self.font_size = sp(self.theme_cls.font_styles[self.font_style][1])
-class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
+class MDTextField(
+ DeclarativeBehavior,
+ ThemableBehavior,
+ TextInput,
+ Validator,
+ AutoFormatTelephoneNumber,
+):
helper_text = StringProperty()
"""
Text for ``helper_text`` mode.
@@ -430,17 +652,185 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
and defaults to `'line'`.
"""
+ phone_mask = StringProperty("")
+
+ validator = OptionProperty(None, options=["date", "email", "time", "phone"])
+ """
+ The type of text field for entering Email, time, etc.
+ Automatically sets the type of the text field as "error" if the user input
+ does not match any of the set validation types.
+ Available options are: `'date'`, `'email'`, `'time'`.
+
+ When using `'date'`, :attr:`date_format` must be defined.
+
+ .. versionadded:: 1.1.0
+
+ .. code-block:: python
+
+ MDTextField:
+ hint_text: "Email"
+ helper_text: "user@gmail.com"
+ validator: "email"
+
+ .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-validator.png
+ :align: center
+
+ .. tabs::
+
+ .. tab:: Declarative KV style
+
+ .. code-block:: python
+
+ from kivy.lang import Builder
+
+ from kivymd.app import MDApp
+
+ KV = '''
+ MDScreen:
+
+ MDBoxLayout:
+ orientation: "vertical"
+ spacing: "20dp"
+ adaptive_height: True
+ size_hint_x: .8
+ pos_hint: {"center_x": .5, "center_y": .5}
+
+ MDTextField:
+ hint_text: "Date dd/mm/yyyy without limits"
+ helper_text: "Enter a valid dd/mm/yyyy date"
+ validator: "date"
+ date_format: "dd/mm/yyyy"
+
+ MDTextField:
+ hint_text: "Date mm/dd/yyyy without limits"
+ helper_text: "Enter a valid mm/dd/yyyy date"
+ validator: "date"
+ date_format: "mm/dd/yyyy"
+
+ MDTextField:
+ hint_text: "Date yyyy/mm/dd without limits"
+ helper_text: "Enter a valid yyyy/mm/dd date"
+ validator: "date"
+ date_format: "yyyy/mm/dd"
+
+ MDTextField:
+ hint_text: "Date dd/mm/yyyy in [01/01/1900, 01/01/2100] interval"
+ helper_text: "Enter a valid dd/mm/yyyy date"
+ validator: "date"
+ date_format: "dd/mm/yyyy"
+ date_interval: "01/01/1900", "01/01/2100"
+
+ MDTextField:
+ hint_text: "Date dd/mm/yyyy in [01/01/1900, None] interval"
+ helper_text: "Enter a valid dd/mm/yyyy date"
+ validator: "date"
+ date_format: "dd/mm/yyyy"
+ date_interval: "01/01/1900", None
+
+ MDTextField:
+ hint_text: "Date dd/mm/yyyy in [None, 01/01/2100] interval"
+ helper_text: "Enter a valid dd/mm/yyyy date"
+ validator: "date"
+ date_format: "dd/mm/yyyy"
+ date_interval: None, "01/01/2100"
+ '''
+
+
+ class Test(MDApp):
+ def build(self):
+ self.theme_cls.theme_style = "Dark"
+ self.theme_cls.primary_palette = "Orange"
+ return Builder.load_string(KV)
+
+
+ Test().run()
+
+ .. tab:: Declarative python style
+
+ .. code-block:: python
+
+ from kivymd.app import MDApp
+ from kivymd.uix.boxlayout import MDBoxLayout
+ from kivymd.uix.screen import MDScreen
+ from kivymd.uix.textfield import MDTextField
+
+
+ class Test(MDApp):
+ def build(self):
+ self.theme_cls.theme_style = "Dark"
+ self.theme_cls.primary_palette = "Orange"
+ return (
+ MDScreen(
+ MDBoxLayout(
+ MDTextField(
+ hint_text="Date dd/mm/yyyy without limits",
+ helper_text="Enter a valid dd/mm/yyyy date",
+ validator="date",
+ date_format="dd/mm/yyyy",
+ ),
+ MDTextField(
+ hint_text="Date mm/dd/yyyy without limits",
+ helper_text="Enter a valid mm/dd/yyyy date",
+ validator="date",
+ date_format="mm/dd/yyyy",
+ ),
+ MDTextField(
+ hint_text="Date yyyy/mm/dd without limits",
+ helper_text="Enter a valid yyyy/mm/dd date",
+ validator="date",
+ date_format="yyyy/mm/dd",
+ ),
+ MDTextField(
+ hint_text="Date dd/mm/yyyy in [01/01/1900, 01/01/2100] interval",
+ helper_text="Enter a valid dd/mm/yyyy date",
+ validator="date",
+ date_format="dd/mm/yyyy",
+ date_interval=["01/01/1900", "01/01/2100"],
+ ),
+ MDTextField(
+ hint_text="Date dd/mm/yyyy in [01/01/1900, None] interval",
+ helper_text="Enter a valid dd/mm/yyyy date",
+ validator="date",
+ date_format="dd/mm/yyyy",
+ date_interval=["01/01/1900", None],
+ ),
+ MDTextField(
+ hint_text="Date dd/mm/yyyy in [None, 01/01/2100] interval",
+ helper_text="Enter a valid dd/mm/yyyy date",
+ validator="date",
+ date_format="dd/mm/yyyy",
+ date_interval=[None, "01/01/2100"],
+ ),
+ orientation="vertical",
+ spacing="20dp",
+ adaptive_height=True,
+ size_hint_x=0.8,
+ pos_hint={"center_x": 0.5, "center_y": 0.5},
+ )
+ )
+ )
+
+
+ Test().run()
+
+ .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-validator-date.png
+ :align: center
+
+ :attr:`validator` is an :class:`~kivy.properties.OptionProperty`
+ and defaults to `None`.
+ """
+
line_color_normal = ColorProperty([0, 0, 0, 0])
"""
- Line color normal (static underline line) in ``rgba`` format.
+ Line color normal (static underline line) in (r, g, b, a) or string format.
.. code-block:: kv
MDTextField:
hint_text: "line_color_normal"
- line_color_normal: 1, 0, 1, 1
+ line_color_normal: "red"
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-line-color-normal.gif
+ .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-line-color-normal.png
:align: center
:attr:`line_color_normal` is an :class:`~kivy.properties.ColorProperty`
@@ -449,13 +839,13 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
line_color_focus = ColorProperty([0, 0, 0, 0])
"""
- Line color focus (active underline line) in ``rgba`` format.
+ Line color focus (active underline line) in (r, g, b, a) or string format.
.. code-block:: kv
MDTextField:
hint_text: "line_color_focus"
- line_color_focus: 0, 1, 0, 1
+ line_color_focus: "red"
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-line-color-focus.gif
:align: center
@@ -474,7 +864,7 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
error_color = ColorProperty([0, 0, 0, 0])
"""
- Error color in ``rgba`` format for ``required = True``.
+ Error color in (r, g, b, a) or string format for ``required = True``.
:attr:`error_color` is an :class:`~kivy.properties.ColorProperty`
and defaults to `[0, 0, 0, 0]`.
@@ -482,7 +872,18 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
fill_color_normal = ColorProperty([0, 0, 0, 0])
"""
- Fill background color in 'fill' mode when text field is out of focus.
+ Fill background color in (r, g, b, a) or string format in 'fill' mode when]
+ text field is out of focus.
+
+ .. code=block:: kv
+
+ MDTextField:
+ hint_text: "Fill mode"
+ mode: "fill"
+ fill_color_normal: "brown"
+
+ .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-fill-color-normal.png
+ :align: center
:attr:`fill_color_normal` is an :class:`~kivy.properties.ColorProperty`
and defaults to `[0, 0, 0, 0]`.
@@ -490,7 +891,18 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
fill_color_focus = ColorProperty([0, 0, 0, 0])
"""
- Fill background color in 'fill' mode when the text field has focus.
+ Fill background color in (r, g, b, a) or string format in 'fill' mode when
+ the text field has focus.
+
+ .. code=block:: kv
+
+ MDTextField:
+ hint_text: "Fill mode"
+ mode: "fill"
+ fill_color_focus: "brown"
+
+ .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-fill-color-focus.gif
+ :align: center
:attr:`fill_color_focus` is an :class:`~kivy.properties.ColorProperty`
and defaults to `[0, 0, 0, 0]`.
@@ -514,7 +926,8 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
hint_text_color_normal = ColorProperty([0, 0, 0, 0])
"""
- Hint text color when text field is out of focus.
+ Hint text color in (r, g, b, a) or string format when text field is out
+ of focus.
.. versionadded:: 1.0.0
@@ -522,9 +935,9 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
MDTextField:
hint_text: "hint_text_color_normal"
- hint_text_color_normal: 0, 1, 0, 1
+ hint_text_color_normal: "red"
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-hint-text-color-normal.gif
+ .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-hint-text-color-normal.png
:align: center
:attr:`hint_text_color_normal` is an :class:`~kivy.properties.ColorProperty`
@@ -533,7 +946,8 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
hint_text_color_focus = ColorProperty([0, 0, 0, 0])
"""
- Hint text color when the text field has focus.
+ Hint text color in (r, g, b, a) or string format when the text field has
+ focus.
.. versionadded:: 1.0.0
@@ -541,7 +955,7 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
MDTextField:
hint_text: "hint_text_color_focus"
- hint_text_color_focus: 0, 1, 0, 1
+ hint_text_color_focus: "red"
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-hint-text-color-focus.gif
:align: center
@@ -552,7 +966,8 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
helper_text_color_normal = ColorProperty([0, 0, 0, 0])
"""
- Helper text color when text field is out of focus.
+ Helper text color in (r, g, b, a) or string format when text field is out
+ of focus.
.. versionadded:: 1.0.0
@@ -561,7 +976,7 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
MDTextField:
helper_text: "helper_text_color_normal"
helper_text_mode: "persistent"
- helper_text_color_normal: 0, 1, 0, 1
+ helper_text_color_normal: "red"
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-helper-text-color-normal.png
:align: center
@@ -572,7 +987,8 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
helper_text_color_focus = ColorProperty([0, 0, 0, 0])
"""
- Helper text color when the text field has focus.
+ Helper text color in (r, g, b, a) or string format when the text field has
+ focus.
.. versionadded:: 1.0.0
@@ -581,7 +997,7 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
MDTextField:
helper_text: "helper_text_color_focus"
helper_text_mode: "persistent"
- helper_text_color_focus: 0, 1, 0, 1
+ helper_text_color_focus: "red"
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-helper-text-color-focus.gif
:align: center
@@ -592,7 +1008,8 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
icon_right_color_normal = ColorProperty([0, 0, 0, 0])
"""
- Color of right icon when text field is out of focus.
+ Color in (r, g, b, a) or string format of right icon when text field is out
+ of focus.
.. versionadded:: 1.0.0
@@ -601,9 +1018,9 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
MDTextField:
icon_right: "language-python"
hint_text: "icon_right_color_normal"
- icon_right_color_normal: 0, 1, 0, 1
+ icon_right_color_normal: "red"
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-icon-right-color-normal.gif
+ .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-icon-right-color-normal.png
:align: center
:attr:`icon_right_color_normal` is an :class:`~kivy.properties.ColorProperty`
@@ -612,7 +1029,8 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
icon_right_color_focus = ColorProperty([0, 0, 0, 0])
"""
- Color of right icon when the text field has focus.
+ Color in (r, g, b, a) or string format of right icon when the text field
+ has focus.
.. versionadded:: 1.0.0
@@ -621,7 +1039,7 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
MDTextField:
icon_right: "language-python"
hint_text: "icon_right_color_focus"
- icon_right_color_focus: 0, 1, 0, 1
+ icon_right_color_focus: "red"
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-icon-right-color-focus.gif
:align: center
@@ -632,47 +1050,30 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
icon_left_color_normal = ColorProperty([0, 0, 0, 0])
"""
- Color of right icon when text field is out of focus.
+ Color in (r, g, b, a) or string format of right icon when text field is out
+ of focus.
.. versionadded:: 1.0.0
- .. code-block:: kv
-
- MDTextField:
- icon_right: "language-python"
- hint_text: "icon_right_color_normal"
- icon_left_color_normal: 0, 1, 0, 1
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-icon-right-color-normal.gif
- :align: center
-
:attr:`icon_left_color_normal` is an :class:`~kivy.properties.ColorProperty`
and defaults to `[0, 0, 0, 0]`.
"""
icon_left_color_focus = ColorProperty([0, 0, 0, 0])
"""
- Color of right icon when the text field has focus.
+ Color in (r, g, b, a) or string format of right icon when the text field
+ has focus.
.. versionadded:: 1.0.0
- .. code-block:: kv
-
- MDTextField:
- icon_right: "language-python"
- hint_text: "icon_right_color_focus"
- icon_right_color_focus: 0, 1, 0, 1
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-icon-right-color-focus.gif
- :align: center
-
:attr:`icon_left_color_focus` is an :class:`~kivy.properties.ColorProperty`
and defaults to `[0, 0, 0, 0]`.
"""
max_length_text_color = ColorProperty([0, 0, 0, 0])
"""
- Text color of the maximum length of characters to be input.
+ Text color in (r, g, b, a) or string format of the maximum length of
+ characters to be input.
.. versionadded:: 1.0.0
@@ -680,10 +1081,10 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
MDTextField:
hint_text: "max_length_text_color"
- max_length_text_color: 0, 1, 0, 1
+ max_length_text_color: "red"
max_text_length: 5
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-max-length-text-color.gif
+ .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-max-length-text-color.png
:align: center
:attr:`max_length_text_color` is an :class:`~kivy.properties.ColorProperty`
@@ -718,7 +1119,7 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
text_color_normal = ColorProperty([0, 0, 0, 0])
"""
- Text color in ``rgba`` format when text field is out of focus.
+ Text color in (r, g, b, a) or string format when text field is out of focus.
.. versionadded:: 1.0.0
@@ -726,9 +1127,9 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
MDTextField:
hint_text: "text_color_normal"
- text_color_normal: 0, 1, 0, 1
+ text_color_normal: "red"
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-text-color-normal.gif
+ .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-text-color-normal.png
:align: center
:attr:`text_color_normal` is an :class:`~kivy.properties.ColorProperty`
@@ -737,7 +1138,7 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
text_color_focus = ColorProperty([0, 0, 0, 0])
"""
- Text color in ``rgba`` format when text field has focus.
+ Text color in (r, g, b, a) or string format when text field has focus.
.. versionadded:: 1.0.0
@@ -745,7 +1146,7 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
MDTextField:
hint_text: "text_color_focus"
- text_color_focus: 0, 1, 0, 1
+ text_color_focus: "red"
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-text-color-focus.gif
:align: center
@@ -879,8 +1280,8 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
text=self.set_text,
)
self.theme_cls.bind(
- primary_color=lambda x, y: self.set_default_colors(0, True),
- theme_style=lambda x, y: self.set_default_colors(0, True),
+ primary_color=self.set_default_colors,
+ theme_style=self.set_default_colors,
)
Clock.schedule_once(self.check_text)
@@ -930,9 +1331,17 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
)
if self.error_color == [0, 0, 0, 0] or updated:
- self.error_color = self.theme_cls.error_color
+ self.error_color = (
+ self.theme_cls.error_color
+ if self.error_color == [0, 0, 0, 0]
+ else self.error_color
+ )
if self.max_length_text_color == [0, 0, 0, 0] or updated:
- self.max_length_text_color = self.theme_cls.disabled_hint_text_color
+ self.max_length_text_color = (
+ self.theme_cls.disabled_hint_text_color
+ if self.max_length_text_color == [0, 0, 0, 0]
+ else self.max_length_text_color
+ )
self._hint_text_color = self.hint_text_color_normal
self._text_color_normal = self.text_color_normal
@@ -1101,8 +1510,11 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
self.text = re.sub("\n", " ", text) if not self.multiline else text
self.set_max_text_length()
+ if self.validator and self.validator == "phone":
+ pass
+ # self.format(self.text)
- if self.text and self.max_length_text_color and self._get_has_error():
+ if (self.text and self.max_length_text_color) or self._get_has_error():
self.error = True
if (
self.text
@@ -1301,22 +1713,34 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
if value_height >= self.max_height and self.max_height:
self.height = self.max_height
- def on_text_color_normal(self, instance_text_field, color: list):
+ def on_text_color_normal(
+ self, instance_text_field, color: Union[list, str]
+ ):
self._text_color_normal = color
- def on_hint_text_color_normal(self, instance_text_field, color: list):
+ def on_hint_text_color_normal(
+ self, instance_text_field, color: Union[list, str]
+ ):
self._hint_text_color = color
- def on_helper_text_color_normal(self, instance_text_field, color: list):
+ def on_helper_text_color_normal(
+ self, instance_text_field, color: Union[list, str]
+ ):
self._helper_text_color = color
- def on_icon_right_color_normal(self, instance_text_field, color: list):
+ def on_icon_right_color_normal(
+ self, instance_text_field, color: Union[list, str]
+ ):
self._icon_right_color = color
- def on_line_color_normal(self, instance_text_field, color: list):
+ def on_line_color_normal(
+ self, instance_text_field, color: Union[list, str]
+ ):
self._line_color_normal = color
- def on_max_length_text_color(self, instance_text_field, color: list):
+ def on_max_length_text_color(
+ self, instance_text_field, color: Union[list, str]
+ ):
self._max_length_text_color = color
def _set_color(self, attr_name: str, color: str, updated: bool) -> None:
@@ -1353,6 +1777,13 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
the :attr:`~MDTextField.required` parameter is set to `True`.
"""
+ if self.validator and self.validator != "phone":
+ has_error = {
+ "date": self.is_date_valid,
+ "email": self.is_email_valid,
+ "time": self.is_time_valid,
+ }[self.validator](self.text)
+ return has_error
if self.max_text_length and len(self.text) > self.max_text_length:
has_error = True
else:
@@ -1367,9 +1798,12 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
if __name__ == "__main__":
+ from kivy.core.window import Window
from kivy.lang import Builder
from kivy.uix.textinput import TextInput
+ Window.size = (800, 750)
+
from kivymd.app import MDApp
KV = """
@@ -1385,41 +1819,53 @@ MDScreen:
MDTextField:
hint_text: "Label"
- helper_text: "Error massage"
+ helper_text: "Error message"
mode: "rectangle"
max_text_length: 5
MDTextField:
icon_left: "git"
hint_text: "Label"
- helper_text: "Error massage"
+ helper_text: "Error message"
mode: "rectangle"
MDTextField:
icon_left: "git"
hint_text: "Label"
- helper_text: "Error massage"
+ helper_text: "Error message"
mode: "fill"
MDTextField:
hint_text: "Label"
- helper_text: "Error massage"
+ helper_text: "Error message"
mode: "fill"
MDTextField:
hint_text: "Label"
- helper_text: "Error massage"
+ helper_text: "Error message"
MDTextField:
icon_left: "git"
hint_text: "Label"
- helper_text: "Error massage"
+ helper_text: "Error message"
MDTextField:
hint_text: "Round mode"
mode: "round"
max_text_length: 15
- helper_text: "Massage"
+ helper_text: "Message"
+
+ MDTextField:
+ hint_text: "Date dd/mm/yyyy in [01/01/1900, 01/01/2100] interval"
+ helper_text: "Enter a valid dd/mm/yyyy date"
+ validator: "date"
+ date_format: "dd/mm/yyyy"
+ date_interval: "01/01/1900", "01/01/2100"
+
+ MDTextField:
+ hint_text: "Email"
+ helper_text: "user@gmail.com"
+ validator: "email"
MDFlatButton:
text: "SET TEXT"
@@ -1429,6 +1875,8 @@ MDScreen:
class Test(MDApp):
def build(self):
+ self.theme_cls.theme_style = "Dark"
+ self.theme_cls.primary_palette = "Orange"
return Builder.load_string(KV)
def set_text(self):
diff --git a/sbapp/kivymd/uix/toolbar/toolbar.py b/sbapp/kivymd/uix/toolbar/toolbar.py
index a1ea57f0..0a48548f 100755
--- a/sbapp/kivymd/uix/toolbar/toolbar.py
+++ b/sbapp/kivymd/uix/toolbar/toolbar.py
@@ -121,8 +121,8 @@ Shadow elevation control
.. code-block:: kv
MDTopAppBar:
- title: "Elevation 10"
- elevation: 10
+ title: "Elevation 4"
+ elevation: 4
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toolbar-7.png
:align: center
@@ -327,7 +327,7 @@ Material design 3 style
:align: center
"""
-__all__ = ("MDTopAppBar", "MDBottomAppBar")
+__all__ = ("MDTopAppBar", "MDBottomAppBar", "ActionTopAppBarButton")
import os
from math import cos, radians, sin
@@ -337,10 +337,8 @@ from kivy.animation import Animation
from kivy.clock import Clock
from kivy.core.window import Window
from kivy.lang import Builder
-from kivy.logger import Logger
from kivy.metrics import dp
from kivy.properties import (
- AliasProperty,
BooleanProperty,
ColorProperty,
ListProperty,
@@ -356,15 +354,15 @@ from kivymd import uix_path
from kivymd.color_definitions import text_colors
from kivymd.theming import ThemableBehavior
from kivymd.uix.behaviors import (
+ CommonElevationBehavior,
DeclarativeBehavior,
- FakeRectangularElevationBehavior,
+ ScaleBehavior,
SpecificBackgroundColorBehavior,
)
from kivymd.uix.button import MDFloatingActionButton, MDIconButton
from kivymd.uix.controllers import WindowController
from kivymd.uix.list import OneLineIconListItem
from kivymd.uix.menu import MDDropdownMenu
-from kivymd.uix.templates import ScaleWidget
from kivymd.uix.tooltip import MDTooltip
from kivymd.utils.set_bars_colors import set_bars_colors
@@ -374,7 +372,7 @@ with open(
Builder.load_string(kv_file.read())
-class ActionBottomAppBarButton(MDFloatingActionButton, ScaleWidget):
+class ActionBottomAppBarButton(MDFloatingActionButton, ScaleBehavior):
"""
Implements a floating action button (FAB) for a toolbar with type 'bottom'.
"""
@@ -409,11 +407,11 @@ class OverFlowMenuItem(OneLineIconListItem):
class NotchedBox(
ThemableBehavior,
- FakeRectangularElevationBehavior,
+ CommonElevationBehavior,
SpecificBackgroundColorBehavior,
BoxLayout,
):
- elevation = NumericProperty(6)
+ elevation = NumericProperty(4)
notch_radius = NumericProperty()
notch_center_x = NumericProperty("100dp")
@@ -961,8 +959,10 @@ class MDTopAppBar(DeclarativeBehavior, NotchedBox, WindowController):
self.icon_color = self.theme_cls.primary_color
self.bind(specific_text_color=self.update_action_bar_text_colors)
- self.theme_cls.bind(material_style=self.update_bar_height)
- self.theme_cls.bind(primary_palette=self.update_md_bg_color)
+ self.theme_cls.bind(
+ material_style=self.update_bar_height,
+ primary_palette=self.update_md_bg_color,
+ )
Clock.schedule_once(
lambda x: self.on_left_action_items(0, self.left_action_items)
@@ -1103,6 +1103,7 @@ class MDTopAppBar(DeclarativeBehavior, NotchedBox, WindowController):
+ self.theme_cls.standard_increment / 2
+ self._shift
)
+ self.shadow_offset = [0, 30]
self.on_mode(None, self.mode)
def on_type_height(self, instance_toolbar, height_type_value: str) -> None:
diff --git a/sbapp/kivymd/uix/tooltip/tooltip.py b/sbapp/kivymd/uix/tooltip/tooltip.py
index 9b05c72e..855119a5 100644
--- a/sbapp/kivymd/uix/tooltip/tooltip.py
+++ b/sbapp/kivymd/uix/tooltip/tooltip.py
@@ -314,7 +314,7 @@ class MDTooltip(ThemableBehavior, HoverBehavior, TouchBehavior):
Clock.schedule_once(self.animation_tooltip_dismiss)
def on_show(self) -> None:
- """Default dismiss event handler."""
+ """Default display event handler."""
def on_dismiss(self) -> None:
"""
diff --git a/sbapp/kivymd/uix/transition/transition.py b/sbapp/kivymd/uix/transition/transition.py
index 49bd6575..169584d7 100644
--- a/sbapp/kivymd/uix/transition/transition.py
+++ b/sbapp/kivymd/uix/transition/transition.py
@@ -35,7 +35,9 @@ __all__ = (
"MDTransitionBase",
)
+from kivy import Logger
from kivy.animation import Animation, AnimationTransition
+from kivy.properties import DictProperty
from kivy.uix.screenmanager import (
ScreenManagerException,
SlideTransition,
@@ -43,13 +45,41 @@ from kivy.uix.screenmanager import (
TransitionBase,
)
+from kivymd.uix.hero import MDHeroFrom, MDHeroTo
from kivymd.uix.screenmanager import MDScreenManager
class MDTransitionBase(TransitionBase):
+ """
+ TransitionBase is used to animate 2 screens within the
+ :class:`~kivymd.uix.screenmanager.MDScreenManager`.
+
+ For more
+ information, see in the :class:`~kivy.uix.screenmanager.TransitionBase`
+ class documentation.
+ """
+
_direction = "in"
- hero_widget = None
- hero_from_widget = None # kivymd.uix.hero.MDHeroFrom object
+ # Collection of child widgets of all 'MDHeroFrom' widgets that are
+ # on the screen, for example:
+ #
+ # MDScreen:
+ #
+ # MDHeroFrom:
+ # tag: "kivymd"
+ #
+ # FitImage:
+ #
+ # MDHeroFrom:
+ # tag: "kivy"
+ #
+ # FitImage:
+ #
+ # {
+ # 'kivy': <kivymd.uix.fitimage.fitimage.FitImage object>,
+ # 'kivymd': <kivymd.uix.fitimage.fitimage.FitImage object>,
+ # }
+ _hero_from_widget_children = DictProperty()
def start(self, instance_screen_manager: MDScreenManager) -> None:
super().start(instance_screen_manager)
@@ -59,66 +89,159 @@ class MDTransitionBase(TransitionBase):
]()
def animated_hero_in(self) -> None:
- if self.manager._heroes_data and self.manager.current_hero:
- self.hero_from_widget = self.manager.get_hero_from_widget()
- self._check_widget_properties()
- self.hero_widget = self.hero_from_widget.children[0]
- self.hero_from_widget.remove_widget(self.hero_widget)
-
- self.hero_widget.pos = self.screen_out.to_widget(
- *self.hero_from_widget.to_window(*self.hero_from_widget.pos)
- )
- self.hero_widget.size = self.hero_from_widget.size
- self.manager.get_root_window().add_widget(self.hero_widget)
-
- Animation(
- size=self.screen_in.hero_to.size,
- d=self.duration,
- pos=self.screen_in.hero_to.pos,
- ).start(self.hero_widget)
- self.hero_from_widget.dispatch(
- "on_transform_in", self.hero_widget, self.duration
- )
+ """Animates the flight of heroes from screen **A** to screen **B**."""
+
+ if self.manager._heroes_data and self.manager.current_heroes:
+ for hero_from_widget in self.manager.get_hero_from_widget():
+ for heroes_tag in self.manager.current_heroes:
+ if heroes_tag == hero_from_widget.tag:
+ self._check_widget_properties(hero_from_widget)
+
+ # Get child widget of the 'MDHeroFrom' container.
+ hero_widget = hero_from_widget.children[0]
+ self._hero_from_widget_children[
+ hero_from_widget.tag
+ ] = hero_widget
+
+ # Removing the child widget from the 'MDHeroFrom'
+ # container.
+ hero_from_widget.remove_widget(hero_widget)
+
+ # We set the size, position of the child widget of the
+ # 'MDHeroFrom' container and add this widget to the
+ # root window.
+ hero_widget.pos = self.screen_out.to_widget(
+ *hero_from_widget.to_window(*hero_from_widget.pos)
+ )
+ hero_widget.size = hero_from_widget.size
+ self.manager.get_root_window().add_widget(hero_widget)
+
+ # Animating widgets added to the root window.
+ if self.screen_in.heroes_to:
+ for hero_to_widget in self.screen_in.heroes_to:
+ self._check_hero_to_widget_tag(
+ hero_to_widget, hero_from_widget
+ )
+ if hero_to_widget.tag == heroes_tag:
+ Animation(
+ size=hero_to_widget.size,
+ d=self.duration,
+ pos=hero_to_widget.pos,
+ ).start(hero_widget)
+ hero_from_widget.dispatch(
+ "on_transform_in",
+ hero_widget,
+ self.duration,
+ )
def animated_hero_out(self) -> None:
- if self.manager._heroes_data and self.manager.current_hero:
- self.screen_out.hero_to.remove_widget(self.hero_widget)
- self.manager.get_root_window().add_widget(self.hero_widget)
-
- self.hero_from_widget.dispatch(
- "on_transform_out", self.hero_widget, self.duration
- )
- Animation(
- pos=self.screen_in.to_widget(
- *self.hero_from_widget.to_window(*self.hero_from_widget.pos)
- ),
- size=self.hero_from_widget.size,
- d=self.duration,
- ).start(self.hero_widget)
+ """Animates the flight of heroes from screen **B** to screen **A**."""
+
+ if (
+ self.manager._heroes_data
+ and self.manager.current_heroes
+ and self.screen_out.heroes_to
+ ):
+
+ for heroes_tag in self.manager.current_heroes:
+ for hero_to_widget in self.screen_out.heroes_to:
+ if hero_to_widget.tag == heroes_tag:
+ hero_from_children = self._hero_from_widget_children[
+ heroes_tag
+ ]
+ hero_to_widget.remove_widget(hero_from_children)
+ self.manager.get_root_window().add_widget(
+ hero_from_children
+ )
+
+ for (
+ hero_from_widget
+ ) in self.manager.get_hero_from_widget():
+ hero_from_widget.dispatch(
+ "on_transform_out",
+ self._hero_from_widget_children[
+ hero_from_widget.tag
+ ],
+ self.duration,
+ )
+ Animation(
+ pos=self.screen_in.to_widget(
+ *hero_from_widget.to_window(
+ *hero_from_widget.pos
+ )
+ ),
+ size=hero_from_widget.size,
+ d=self.duration,
+ ).start(
+ self._hero_from_widget_children[
+ hero_from_widget.tag
+ ]
+ )
def on_complete(self) -> None:
+ """
+ Override method.
+ See :attr:`kivy.uix.screenmanager.TransitionBase.on_complete'.
+ """
+
super().on_complete()
+ if self.manager._heroes_data and self.manager.current_heroes:
+ for hero_from_widget in self.manager.get_hero_from_widget():
+ for heroes_tag in self.manager.current_heroes:
+ if heroes_tag == hero_from_widget.tag:
+ hero_from_children = self._hero_from_widget_children[
+ heroes_tag
+ ]
+ self.manager.get_root_window().remove_widget(
+ hero_from_children
+ )
+
+ # Adding a child widget from the 'MDHeraFrom' container
+ # to the 'MDHeroTo' container.
+ if self._direction == "in":
+ for hero_to_widget in self.screen_in.heroes_to:
+ if hero_to_widget.tag == heroes_tag:
+ hero_to_widget.add_widget(
+ hero_from_children
+ )
+ # Restores the child widget for the 'MDHeraFrom'
+ # container.
+ elif self._direction == "out":
+ hero_from_widget.add_widget(hero_from_children)
+
if self._direction == "out":
self._direction = "in"
- if self.manager._heroes_data and self.manager.current_hero:
- self.manager.get_root_window().remove_widget(self.hero_widget)
- self.hero_from_widget.add_widget(self.hero_widget)
else:
self._direction = "out"
- if self.manager._heroes_data and self.manager.current_hero:
- self.manager.get_root_window().remove_widget(self.hero_widget)
- self.screen_in.hero_to.add_widget(self.hero_widget)
- def _check_widget_properties(self):
- if not self.screen_in.hero_to:
+ # Checks the attributes for the 'self.screen_in' screen.
+ # Called from the animated_hero_in method.
+ def _check_widget_properties(self, hero_from_widget: MDHeroFrom):
+ if not self.screen_in.heroes_to:
raise Exception(
- f"The `hero_to` attribute is not specified for screen {self.screen_in}"
+ f"The `heroes_to` attribute is not specified for screen "
+ f"{self.screen_in}"
)
- if len(self.hero_from_widget.children) > 1:
+ # The 'MDHeroFrom' widget allows you to place only one widget in
+ # itself.
+ if len(hero_from_widget.children) > 1:
raise Exception(
- f"{self.hero_from_widget.__class__} accept only one widget"
+ f"{hero_from_widget.__class__} accept only one widget"
+ )
+
+ # For new API support.
+ def _check_hero_to_widget_tag(
+ self, hero_to_widget: MDHeroTo, hero_from_widget: MDHeroFrom
+ ) -> None:
+ if not hero_to_widget.tag:
+ Logger.warning(
+ "KivyMD: "
+ f"Set the tag '{hero_from_widget.tag}' "
+ f"for the {hero_to_widget} widget to the same "
+ f"as for the {hero_from_widget} widget"
)
+ hero_to_widget.tag = hero_from_widget.tag
class MDSwapTransition(SwapTransition, MDTransitionBase):
diff --git a/sbapp/kivymd/uix/widget.py b/sbapp/kivymd/uix/widget.py
index 19a5d60d..044722bb 100644
--- a/sbapp/kivymd/uix/widget.py
+++ b/sbapp/kivymd/uix/widget.py
@@ -36,11 +36,13 @@ MDWidget
__all__ = ("MDWidget",)
+from kivy.uix.widget import Widget
+
from kivymd.uix import MDAdaptiveWidget
from kivymd.uix.behaviors import DeclarativeBehavior
-class MDWidget(DeclarativeBehavior, MDAdaptiveWidget):
+class MDWidget(DeclarativeBehavior, MDAdaptiveWidget, Widget):
"""
See :class:`~kivy.uix.Widget` class documentation for more information.
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────